move test_buf_pipe.py unit test, shuffle nmutil
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 2 May 2019 13:03:37 +0000 (14:03 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 2 May 2019 13:03:37 +0000 (14:03 +0100)
31 files changed:
src/ieee754/add/concurrentunit.py
src/ieee754/add/example_buf_pipe.py [deleted file]
src/ieee754/add/fadd_state.py
src/ieee754/add/fmul.py
src/ieee754/add/fpbase.py
src/ieee754/add/iocontrol.py [deleted file]
src/ieee754/add/nmigen_div_experiment.py
src/ieee754/add/queue.py [deleted file]
src/ieee754/add/record_experiment.py
src/ieee754/add/stageapi.py [deleted file]
src/ieee754/add/test_buf_pipe.py [deleted file]
src/ieee754/add/test_fsm_experiment.py
src/ieee754/add/test_inout_mux_pipe.py
src/ieee754/add/test_outmux_pipe.py
src/ieee754/add/test_prioritymux_pipe.py
src/ieee754/fpadd/addstages.py
src/ieee754/fpadd/pipeline.py
src/ieee754/fpadd/specialcases.py
src/ieee754/fpadd/statemachine.py
src/ieee754/fpcommon/getop.py
src/ieee754/fpcommon/normtopack.py
src/ieee754/fpcommon/pack.py
src/nmutil/iocontrol.py [new file with mode: 0644]
src/nmutil/multipipe.py
src/nmutil/nmoperator.py
src/nmutil/pipeline.py
src/nmutil/queue.py [new file with mode: 0644]
src/nmutil/singlepipe.py
src/nmutil/stageapi.py [new file with mode: 0644]
src/nmutil/test/example_buf_pipe.py [new file with mode: 0644]
src/nmutil/test/test_buf_pipe.py [new file with mode: 0644]

index dbe4a964ef4cea1772a23c661f8313754b026cab..9419d528b163ceba4843c63a523db54e90475224 100644 (file)
@@ -6,9 +6,9 @@ from math import log
 from nmigen import Module
 from nmigen.cli import main, verilog
 
 from nmigen import Module
 from nmigen.cli import main, verilog
 
-from singlepipe import PassThroughStage
-from multipipe import CombMuxOutPipe
-from multipipe import PriorityCombMuxInPipe
+from nmutil.singlepipe import PassThroughStage
+from nmutil.multipipe import CombMuxOutPipe
+from nmutil.multipipe import PriorityCombMuxInPipe
 
 from ieee754.fpcommon.getop import FPADDBaseData
 from ieee754.fpcommon.denorm import FPSCData
 
 from ieee754.fpcommon.getop import FPADDBaseData
 from ieee754.fpcommon.denorm import FPSCData
diff --git a/src/ieee754/add/example_buf_pipe.py b/src/ieee754/add/example_buf_pipe.py
deleted file mode 100644 (file)
index 4bb7cdf..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-""" Pipeline and BufferedHandshake examples
-"""
-
-from nmoperator import eq
-from iocontrol import (PrevControl, NextControl)
-from singlepipe import (PrevControl, NextControl, ControlBase,
-                        StageCls, Stage, StageChain,
-                        BufferedHandshake, UnbufferedPipeline)
-
-from nmigen import Signal, Module
-from nmigen.cli import verilog, rtlil
-
-
-class ExampleAddStage(StageCls):
-    """ an example of how to use the buffered pipeline, as a class instance
-    """
-
-    def ispec(self):
-        """ returns a tuple of input signals which will be the incoming data
-        """
-        return (Signal(16), Signal(16))
-
-    def ospec(self):
-        """ returns an output signal which will happen to contain the sum
-            of the two inputs
-        """
-        return Signal(16)
-
-    def process(self, i):
-        """ process the input data (sums the values in the tuple) and returns it
-        """
-        return i[0] + i[1]
-
-
-class ExampleBufPipeAdd(BufferedHandshake):
-    """ an example of how to use the buffered pipeline, using a class instance
-    """
-
-    def __init__(self):
-        addstage = ExampleAddStage()
-        BufferedHandshake.__init__(self, addstage)
-
-
-class ExampleStage(Stage):
-    """ an example of how to use the buffered pipeline, in a static class
-        fashion
-    """
-
-    def ispec():
-        return Signal(16, name="example_input_signal")
-
-    def ospec():
-        return Signal(16, name="example_output_signal")
-
-    def process(i):
-        """ process the input data and returns it (adds 1)
-        """
-        return i + 1
-
-
-class ExampleStageCls(StageCls):
-    """ an example of how to use the buffered pipeline, in a static class
-        fashion
-    """
-
-    def ispec(self):
-        return Signal(16, name="example_input_signal")
-
-    def ospec(self):
-        return Signal(16, name="example_output_signal")
-
-    def process(self, i):
-        """ process the input data and returns it (adds 1)
-        """
-        return i + 1
-
-
-class ExampleBufPipe(BufferedHandshake):
-    """ an example of how to use the buffered pipeline.
-    """
-
-    def __init__(self):
-        BufferedHandshake.__init__(self, ExampleStage)
-
-
-class ExamplePipeline(UnbufferedPipeline):
-    """ an example of how to use the unbuffered pipeline.
-    """
-
-    def __init__(self):
-        UnbufferedPipeline.__init__(self, ExampleStage)
-
-
-if __name__ == '__main__':
-    dut = ExampleBufPipe()
-    vl = rtlil.convert(dut, ports=dut.ports())
-    with open("test_bufpipe.il", "w") as f:
-        f.write(vl)
-
-    dut = ExamplePipeline()
-    vl = rtlil.convert(dut, ports=dut.ports())
-    with open("test_combpipe.il", "w") as f:
-        f.write(vl)
index 7ad88786027a4fdef707df6847e0c6447c455e30..be4f7d579566fc76866db3e924398182753b7c65 100644 (file)
@@ -7,7 +7,7 @@ from nmigen.cli import main, verilog
 
 from fpbase import FPNumIn, FPNumOut, FPOp, Overflow, FPBase
 
 
 from fpbase import FPNumIn, FPNumOut, FPOp, Overflow, FPBase
 
-from singlepipe import eq
+from nmutil.singlepipe import eq
 
 
 class FPADD(FPBase):
 
 
 class FPADD(FPBase):
index a2ba41e75eb9bbf081d20158865171c21911940d..abe6f613b75ca57882c16098ddce180eae4775cb 100644 (file)
@@ -3,7 +3,7 @@ from nmigen.cli import main, verilog
 
 from fpbase import FPNumIn, FPNumOut, FPOp, Overflow, FPBase, FPState
 from fpcommon.getop import FPGetOp
 
 from fpbase import FPNumIn, FPNumOut, FPOp, Overflow, FPBase, FPState
 from fpcommon.getop import FPGetOp
-from singlepipe import eq
+from nmutil.singlepipe import eq
 
 
 class FPMUL(FPBase):
 
 
 class FPMUL(FPBase):
index f49085921d914d600dea950d3707ab924eb0272b..dbd4da271b134cb096cf04667e298ce4d66c3598 100644 (file)
@@ -7,7 +7,7 @@ from math import log
 from operator import or_
 from functools import reduce
 
 from operator import or_
 from functools import reduce
 
-from singlepipe import PrevControl, NextControl
+from nmutil.singlepipe import PrevControl, NextControl
 from pipeline import ObjectProxy
 
 
 from pipeline import ObjectProxy
 
 
diff --git a/src/ieee754/add/iocontrol.py b/src/ieee754/add/iocontrol.py
deleted file mode 100644 (file)
index 3d823c9..0000000
+++ /dev/null
@@ -1,306 +0,0 @@
-""" IO Control API
-
-    Associated development bugs:
-    * http://bugs.libre-riscv.org/show_bug.cgi?id=64
-    * http://bugs.libre-riscv.org/show_bug.cgi?id=57
-
-    Stage API:
-    ---------
-
-    stage requires compliance with a strict API that may be
-    implemented in several means, including as a static class.
-
-    Stages do not HOLD data, and they definitely do not contain
-    signalling (ready/valid).  They do however specify the FORMAT
-    of the incoming and outgoing data, and they provide a means to
-    PROCESS that data (from incoming format to outgoing format).
-
-    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.  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 worked examples.
-
-    * ospec() - Output data format specification.
-                format requirements identical to ispec.
-
-    * process(m, i) - Optional function for processing ispec-formatted data.
-                returns a combinatorial block of a result that
-                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
-                for adding those submodules to the module (m).
-                the submodules must be combinatorial blocks and
-                must have their inputs and output linked combinatorially.
-
-    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.  See Liskov Substitution Principle:
-    https://en.wikipedia.org/wiki/Liskov_substitution_principle
-
-    StageChain:
-    ----------
-
-    A useful combinatorial wrapper around stages that chains them together
-    and then presents a Stage-API-conformant interface.  By presenting
-    the same API as the stages it wraps, it can clearly be used recursively.
-
-    ControlBase:
-    -----------
-
-    The base class for pipelines.  Contains previous and next ready/valid/data.
-    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, Module, Value, Elaboratable
-from nmigen.cli import verilog, rtlil
-from nmigen.hdl.rec import Record
-
-from collections.abc import Sequence, Iterable
-from collections import OrderedDict
-
-import nmoperator
-
-
-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():  # OrderedDict so order is preserved
-            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): # being called "keys" would be much better
-        return list(self)
-
-
-class RecordObject(Record):
-    def __init__(self, layout=None, name=None):
-        Record.__init__(self, layout=layout or [], name=None)
-
-    def __setattr__(self, k, v):
-        #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)}
-        elif isinstance(v, Value):
-            newlayout = {k: (k, v.shape())}
-        else:
-            newlayout = {k: (k, nmoperator.shape(v))}
-        self.layout.fields.update(newlayout)
-
-    def __iter__(self):
-        for x in self.fields.values(): # remember: fields is an OrderedDict
-            if isinstance(x, Iterable):
-                yield from x           # a bit like flatten (nmigen.tools)
-            else:
-                yield x
-
-    def ports(self): # would be better being called "keys"
-        return list(self)
-
-
-class PrevControl(Elaboratable):
-    """ contains signals that come *from* the previous stage (both in and out)
-        * valid_i: 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".
-        * ready_o: output to next stage indicating readiness to accept data
-        * 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.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.trigger = Signal(reset_less=True)
-
-    @property
-    def ready_o(self):
-        """ public-facing API: indicates (externally) that stage is ready
-        """
-        if self.stage_ctl:
-            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, fn=None, do_data=True):
-        """ internal helper function to connect stage to an input source.
-            do not use to connect stage-to-stage!
-        """
-        valid_i = prev.valid_i if direct else prev.valid_i_test
-        res = [self.valid_i.eq(valid_i),
-               prev.ready_o.eq(self.ready_o)]
-        if do_data is False:
-            return res
-        data_i = fn(prev.data_i) if fn is not None else prev.data_i
-        return res + [nmoperator.eq(self.data_i, data_i)]
-
-    @property
-    def valid_i_test(self):
-        vlen = len(self.valid_i)
-        if vlen > 1:
-            # multi-bit case: valid only when valid_i is all 1s
-            all1s = Const(-1, (len(self.valid_i), False))
-            valid_i = (self.valid_i == all1s)
-        else:
-            # single-bit valid_i case
-            valid_i = self.valid_i
-
-        # when stage indicates not ready, incoming data
-        # must "appear" to be not ready too
-        if self.stage_ctl:
-            valid_i = valid_i & self.s_ready_o
-
-        return valid_i
-
-    def elaborate(self, platform):
-        m = Module()
-        m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
-        return m
-
-    def eq(self, i):
-        return [nmoperator.eq(self.data_i, i.data_i),
-                self.ready_o.eq(i.ready_o),
-                self.valid_i.eq(i.valid_i)]
-
-    def __iter__(self):
-        yield self.valid_i
-        yield self.ready_o
-        if hasattr(self.data_i, "ports"):
-            yield from self.data_i.ports()
-        elif isinstance(self.data_i, Sequence):
-            yield from self.data_i
-        else:
-            yield self.data_i
-
-    def ports(self):
-        return list(self)
-
-
-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 - MUST be added by the USER of this class
-    """
-    def __init__(self, stage_ctl=False):
-        self.stage_ctl = stage_ctl
-        self.valid_o = Signal(name="n_valid_o") # self out>>  next
-        self.ready_i = Signal(name="n_ready_i") # self <<in   next
-        self.data_o = 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 ready_i_test(self):
-        if self.stage_ctl:
-            return self.ready_i & self.d_valid
-        return self.ready_i
-
-    def connect_to_next(self, nxt, do_data=True):
-        """ helper function to connect to the next stage data/valid/ready.
-            data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
-            use this when connecting stage-to-stage
-        """
-        res = [nxt.valid_i.eq(self.valid_o),
-               self.ready_i.eq(nxt.ready_o)]
-        if do_data:
-            res.append(nmoperator.eq(nxt.data_i, self.data_o))
-        return res
-
-    def _connect_out(self, nxt, direct=False, fn=None, do_data=True):
-        """ internal helper function to connect stage to an output source.
-            do not use to connect stage-to-stage!
-        """
-        ready_i = nxt.ready_i if direct else nxt.ready_i_test
-        res = [nxt.valid_o.eq(self.valid_o),
-               self.ready_i.eq(ready_i)]
-        if not do_data:
-            return res
-        data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
-        return res + [nmoperator.eq(data_o, self.data_o)]
-
-    def elaborate(self, platform):
-        m = Module()
-        m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
-        return m
-
-    def __iter__(self):
-        yield self.ready_i
-        yield self.valid_o
-        if hasattr(self.data_o, "ports"):
-            yield from self.data_o.ports()
-        elif isinstance(self.data_o, Sequence):
-            yield from self.data_o
-        else:
-            yield self.data_o
-
-    def ports(self):
-        return list(self)
-
index a7e215cb888817b750426af676a7825552dee431..a19decd5850b2c54c8205b2aeb89d8a8d8ebfb97 100644 (file)
@@ -6,7 +6,7 @@ from nmigen import Module, Signal, Const, Cat
 from nmigen.cli import main, verilog
 
 from fpbase import FPNumIn, FPNumOut, FPOpIn, FPOpOut, Overflow, FPBase, FPState
 from nmigen.cli import main, verilog
 
 from fpbase import FPNumIn, FPNumOut, FPOpIn, FPOpOut, Overflow, FPBase, FPState
-from singlepipe import eq
+from nmutil.singlepipe import eq
 
 class Div:
     def __init__(self, width):
 
 class Div:
     def __init__(self, width):
diff --git a/src/ieee754/add/queue.py b/src/ieee754/add/queue.py
deleted file mode 100644 (file)
index 0038953..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright (c) 2014 - 2019 The Regents of the University of
-# California (Regents). All Rights Reserved.  Redistribution and use in
-# source and binary forms, with or without modification, are permitted
-# provided that the following conditions are met:
-#    * Redistributions of source code must retain the above
-#      copyright notice, this list of conditions and the following
-#      two paragraphs of disclaimer.
-#    * Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      two paragraphs of disclaimer in the documentation and/or other materials
-#      provided with the distribution.
-#    * Neither the name of the Regents nor the names of its contributors
-#      may be used to endorse or promote products derived from this
-#      software without specific prior written permission.
-# IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
-# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
-# ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
-# REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
-# ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION
-# TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
-# MODIFICATIONS.
-
-from nmigen import Module, Signal, Memory, Mux, Elaboratable
-from nmigen.tools import bits_for
-from nmigen.cli import main
-from nmigen.lib.fifo import FIFOInterface
-
-# translated from https://github.com/freechipsproject/chisel3/blob/a4a29e29c3f1eed18f851dcf10bdc845571dfcb6/src/main/scala/chisel3/util/Decoupled.scala#L185   # noqa
-
-
-class Queue(FIFOInterface, Elaboratable):
-    def __init__(self, width, depth, fwft=True, pipe=False):
-        """ Queue (FIFO) with pipe mode and first-write fall-through capability
-
-            * :width: width of Queue data in/out
-            * :depth: queue depth.  NOTE: may be set to 0 (this is ok)
-            * :fwft : first-write, fall-through mode (Chisel Queue "flow" mode)
-            * :pipe : pipe mode.  NOTE: this mode can cause unanticipated
-                      problems.  when read is enabled, so is writeable.
-                      therefore if read is enabled, the data ABSOLUTELY MUST
-                      be read.
-
-            fwft mode = True basically means that the data may be transferred
-            combinatorially from input to output.
-
-            Attributes:
-            * level: available free space (number of unread entries)
-
-            din  = enq_data, writable  = enq_ready, we = enq_valid
-            dout = deq_data, re = deq_ready, readable = deq_valid
-        """
-        FIFOInterface.__init__(self, width, depth, fwft)
-        self.pipe = pipe
-        self.depth = depth
-        self.level = Signal(bits_for(depth))
-
-    def elaborate(self, platform):
-        m = Module()
-
-        # set up an SRAM.  XXX bug in Memory: cannot create SRAM of depth 1
-        ram = Memory(self.width, self.depth if self.depth > 1 else 2)
-        m.submodules.ram_read = ram_read = ram.read_port(synchronous=False)
-        m.submodules.ram_write = ram_write = ram.write_port()
-
-        # convenience names
-        p_ready_o = self.writable
-        p_valid_i = self.we
-        enq_data = self.din
-
-        n_valid_o = self.readable
-        n_ready_i = self.re
-        deq_data = self.dout
-
-        # intermediaries
-        ptr_width = bits_for(self.depth - 1) if self.depth > 1 else 0
-        enq_ptr = Signal(ptr_width) # cyclic pointer to "insert" point (wrport)
-        deq_ptr = Signal(ptr_width) # cyclic pointer to "remove" point (rdport)
-        maybe_full = Signal() # not reset_less (set by sync)
-
-        # temporaries
-        do_enq = Signal(reset_less=True)
-        do_deq = Signal(reset_less=True)
-        ptr_diff = Signal(ptr_width)
-        ptr_match = Signal(reset_less=True)
-        empty = Signal(reset_less=True)
-        full = Signal(reset_less=True)
-        enq_max = Signal(reset_less=True)
-        deq_max = Signal(reset_less=True)
-
-        m.d.comb += [ptr_match.eq(enq_ptr == deq_ptr), # read-ptr = write-ptr
-                     ptr_diff.eq(enq_ptr - deq_ptr),
-                     enq_max.eq(enq_ptr == self.depth - 1),
-                     deq_max.eq(deq_ptr == self.depth - 1),
-                     empty.eq(ptr_match & ~maybe_full),
-                     full.eq(ptr_match & maybe_full),
-                     do_enq.eq(p_ready_o & p_valid_i), # write conditions ok
-                     do_deq.eq(n_ready_i & n_valid_o), # read conditions ok
-
-                     # set readable and writable (NOTE: see pipe mode below)
-                     n_valid_o.eq(~empty), # cannot read if empty!
-                     p_ready_o.eq(~full),  # cannot write if full!
-
-                     # set up memory and connect to input and output
-                     ram_write.addr.eq(enq_ptr),
-                     ram_write.data.eq(enq_data),
-                     ram_write.en.eq(do_enq),
-                     ram_read.addr.eq(deq_ptr),
-                     deq_data.eq(ram_read.data) # NOTE: overridden in fwft mode
-                    ]
-
-        # under write conditions, SRAM write-pointer moves on next clock
-        with m.If(do_enq):
-            m.d.sync += enq_ptr.eq(Mux(enq_max, 0, enq_ptr+1))
-
-        # under read conditions, SRAM read-pointer moves on next clock
-        with m.If(do_deq):
-            m.d.sync += deq_ptr.eq(Mux(deq_max, 0, deq_ptr+1))
-
-        # if read-but-not-write or write-but-not-read, maybe_full set
-        with m.If(do_enq != do_deq):
-            m.d.sync += maybe_full.eq(do_enq)
-
-        # first-word fall-through: same as "flow" parameter in Chisel3 Queue
-        # basically instead of relying on the Memory characteristics (which
-        # in FPGAs do not have write-through), then when the queue is empty
-        # take the output directly from the input, i.e. *bypass* the SRAM.
-        # this done combinatorially to give the exact same characteristics
-        # as Memory "write-through"... without relying on a changing API
-        if self.fwft:
-            with m.If(p_valid_i):
-                m.d.comb += n_valid_o.eq(1)
-            with m.If(empty):
-                m.d.comb += deq_data.eq(enq_data)
-                m.d.comb += do_deq.eq(0)
-                with m.If(n_ready_i):
-                    m.d.comb += do_enq.eq(0)
-
-        # pipe mode: if next stage says it's ready (readable), we
-        #            *must* declare the input ready (writeable).
-        if self.pipe:
-            with m.If(n_ready_i):
-                m.d.comb += p_ready_o.eq(1)
-
-        # set the count (available free space), optimise on power-of-two
-        if self.depth == 1 << ptr_width:  # is depth a power of 2
-            m.d.comb += self.level.eq(
-                Mux(maybe_full & ptr_match, self.depth, 0) | ptr_diff)
-        else:
-            m.d.comb += self.level.eq(Mux(ptr_match,
-                                          Mux(maybe_full, self.depth, 0),
-                                          Mux(deq_ptr > enq_ptr,
-                                              self.depth + ptr_diff,
-                                              ptr_diff)))
-
-        return m
-
-
-if __name__ == "__main__":
-    reg_stage = Queue(1, 1, pipe=True)
-    break_ready_chain_stage = Queue(1, 1, pipe=True, fwft=True)
-    m = Module()
-    ports = []
-
-    def queue_ports(queue, name_prefix):
-        retval = []
-        for name in ["level",
-                     "dout",
-                     "readable",
-                     "writable"]:
-            port = getattr(queue, name)
-            signal = Signal(port.shape(), name=name_prefix+name)
-            m.d.comb += signal.eq(port)
-            retval.append(signal)
-        for name in ["re",
-                     "din",
-                     "we"]:
-            port = getattr(queue, name)
-            signal = Signal(port.shape(), name=name_prefix+name)
-            m.d.comb += port.eq(signal)
-            retval.append(signal)
-        return retval
-
-    m.submodules.reg_stage = reg_stage
-    ports += queue_ports(reg_stage, "reg_stage_")
-    m.submodules.break_ready_chain_stage = break_ready_chain_stage
-    ports += queue_ports(break_ready_chain_stage, "break_ready_chain_stage_")
-    main(m, ports=ports)
index 1789c3bd8a6125819153921da42dcf9f6b87a14f..3d486c7d0b097e039b29985b9964db677490f9b7 100644 (file)
@@ -3,7 +3,7 @@ from nmigen.hdl.rec import Record, Layout, DIR_NONE
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 from nmigen.compat.fhdl.bitcontainer import value_bits_sign
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 from nmigen.compat.fhdl.bitcontainer import value_bits_sign
-from singlepipe import cat, RecordObject
+from nmutil.singlepipe import cat, RecordObject
 
 
 class RecordTest:
 
 
 class RecordTest:
diff --git a/src/ieee754/add/stageapi.py b/src/ieee754/add/stageapi.py
deleted file mode 100644 (file)
index 9651bf7..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-""" Stage API
-
-    Associated development bugs:
-    * http://bugs.libre-riscv.org/show_bug.cgi?id=64
-    * http://bugs.libre-riscv.org/show_bug.cgi?id=57
-
-    Stage API:
-    ---------
-
-    stage requires compliance with a strict API that may be
-    implemented in several means, including as a static class.
-
-    Stages do not HOLD data, and they definitely do not contain
-    signalling (ready/valid).  They do however specify the FORMAT
-    of the incoming and outgoing data, and they provide a means to
-    PROCESS that data (from incoming format to outgoing format).
-
-    Stage Blocks really should be combinatorial blocks (Moore FSMs).
-    It would be ok to have input come in from sync'd sources
-    (clock-driven, Mealy FSMs) 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.
-    https://en.wikipedia.org/wiki/Moore_machine
-    https://en.wikipedia.org/wiki/Mealy_machine
-
-    the methods of a stage instance must be as follows:
-
-    * 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 worked examples.
-
-    * ospec() - Output data format specification.
-                format requirements identical to ispec.
-
-    * process(m, i) - Optional function for processing ispec-formatted data.
-                returns a combinatorial block of a result that
-                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
-                for adding those submodules to the module (m).
-                the submodules must be combinatorial blocks and
-                must have their inputs and output linked combinatorially.
-
-    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.  See Liskov Substitution Principle:
-    https://en.wikipedia.org/wiki/Liskov_substitution_principle
-
-    StageChain:
-    ----------
-
-    A useful combinatorial wrapper around stages that chains them together
-    and then presents a Stage-API-conformant interface.  By presenting
-    the same API as the stages it wraps, it can clearly be used recursively.
-
-    StageHelper:
-    ----------
-
-    A convenience wrapper around a Stage-API-compliant "thing" which
-    complies with the Stage API and provides mandatory versions of
-    all the optional bits.
-"""
-
-from abc import ABCMeta, abstractmethod
-import inspect
-
-import nmoperator
-
-
-def _spec(fn, name=None):
-    """ useful function that determines if "fn" has an argument "name".
-        if so, fn(name) is called otherwise fn() is called.
-
-        means that ispec and ospec can be declared with *or without*
-        a name argument.  normally it would be necessary to have
-        "ispec(name=None)" to achieve the same effect.
-    """
-    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 StageCls(metaclass=ABCMeta):
-    """ Class-based "Stage" API.  requires instantiation (after derivation)
-
-        see "Stage API" above..  Note: python does *not* require derivation
-        from this class.  All that is required is that the pipelines *have*
-        the functions listed in this class.  Derivation from this class
-        is therefore merely a "courtesy" to maintainers.
-    """
-    @abstractmethod
-    def ispec(self): pass       # REQUIRED
-    @abstractmethod
-    def ospec(self): pass       # REQUIRED
-    #@abstractmethod
-    #def setup(self, m, i): pass # OPTIONAL
-    #@abstractmethod
-    #def process(self, i): pass  # OPTIONAL
-
-
-class Stage(metaclass=ABCMeta):
-    """ Static "Stage" API.  does not require instantiation (after derivation)
-
-        see "Stage API" above.  Note: python does *not* require derivation
-        from this class.  All that is required is that the pipelines *have*
-        the functions listed in this class.  Derivation from this class
-        is therefore merely a "courtesy" to maintainers.
-    """
-    @staticmethod
-    @abstractmethod
-    def ispec(): pass
-
-    @staticmethod
-    @abstractmethod
-    def ospec(): pass
-
-    #@staticmethod
-    #@abstractmethod
-    #def setup(m, i): pass
-
-    #@staticmethod
-    #@abstractmethod
-    #def process(i): pass
-
-
-class StageHelper(Stage):
-    """ a convenience wrapper around something that is Stage-API-compliant.
-        (that "something" may be a static class, for example).
-
-        StageHelper happens to also be compliant with the Stage API,
-        it differs from the stage that it wraps in that all the "optional"
-        functions are provided (hence the designation "convenience wrapper")
-    """
-    def __init__(self, stage):
-        self.stage = stage
-        self._ispecfn = None
-        self._ospecfn = None
-        if stage is not None:
-            self.set_specs(self, self)
-
-    def ospec(self, name):
-        assert self._ospecfn is not None
-        return _spec(self._ospecfn, name)
-
-    def ispec(self, name):
-        assert self._ispecfn is not None
-        return _spec(self._ispecfn, name)
-
-    def set_specs(self, p, n):
-        """ sets up the ispecfn and ospecfn for getting input and output data
-        """
-        if hasattr(p, "stage"):
-            p = p.stage
-        if hasattr(n, "stage"):
-            n = n.stage
-        self._ispecfn = p.ispec
-        self._ospecfn = n.ospec
-
-    def new_specs(self, name):
-        """ allocates new ispec and ospec pair
-        """
-        return (_spec(self.ispec, "%s_i" % name),
-                _spec(self.ospec, "%s_o" % name))
-
-    def process(self, i):
-        if self.stage and hasattr(self.stage, "process"):
-            return self.stage.process(i)
-        return i
-
-    def setup(self, m, i):
-        if self.stage is not None and hasattr(self.stage, "setup"):
-            self.stage.setup(m, i)
-
-    def _postprocess(self, i): # XXX DISABLED
-        return i # RETURNS INPUT
-        if hasattr(self.stage, "postprocess"):
-            return self.stage.postprocess(i)
-        return i
-
-
-class StageChain(StageHelper):
-    """ pass in a list of stages, and they will automatically be
-        chained together via their input and output specs into a
-        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.)
-        * 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: 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
-                         "driving from two sources, module is being flattened"
-                         will be issued.
-
-        NOTE: do NOT use StageChain with combinatorial blocks that have
-        side-effects (state-based / clock-based input) or conditional
-        (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
-        StageHelper.__init__(self, None)
-        self.setup = self._sa_setup if specallocate else self._na_setup
-        self.set_specs(self.chain[0], self.chain[-1])
-
-    def _sa_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
-            ofn = self.chain[idx].ospec     # last assignment survives
-            o = _spec(ofn, 'chainin%d' % idx)
-            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 += nmoperator.eq(i, o) # assign to next input
-        self.o = o
-        return self.o                       # last loop is the output
-
-    def _na_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
-            i = o = c.process(i)            # store input into "o"
-        self.o = o
-        return self.o                       # last loop is the output
-
-    def process(self, i):
-        return self.o # conform to Stage API: return last-loop output
-
-
diff --git a/src/ieee754/add/test_buf_pipe.py b/src/ieee754/add/test_buf_pipe.py
deleted file mode 100644 (file)
index 37f2b31..0000000
+++ /dev/null
@@ -1,1308 +0,0 @@
-""" Unit tests for Buffered and Unbuffered pipelines
-
-    contains useful worked examples of how to use the Pipeline API,
-    including:
-
-    * Combinatorial Stage "Chaining"
-    * class-based data stages
-    * nmigen module-based data stages
-    * special nmigen module-based data stage, where the stage *is* the module
-    * Record-based data stages
-    * static-class data stages
-    * multi-stage pipelines (and how to connect them)
-    * how to *use* the pipelines (see Test5) - how to get data in and out
-
-"""
-
-from nmigen import Module, Signal, Mux, Const, Elaboratable
-from nmigen.hdl.rec import Record
-from nmigen.compat.sim import run_simulation
-from nmigen.cli import verilog, rtlil
-
-from example_buf_pipe import ExampleBufPipe, ExampleBufPipeAdd
-from example_buf_pipe import ExamplePipeline, UnbufferedPipeline
-from example_buf_pipe import ExampleStageCls
-from example_buf_pipe import PrevControl, NextControl, BufferedHandshake
-from example_buf_pipe import StageChain, ControlBase, StageCls
-from singlepipe import UnbufferedPipeline2
-from singlepipe import SimpleHandshake
-from singlepipe import PassThroughHandshake
-from singlepipe import PassThroughStage
-from singlepipe import FIFOControl
-from singlepipe import RecordObject
-
-from random import randint, seed
-
-#seed(4)
-
-
-def check_o_n_valid(dut, val):
-    o_n_valid = yield dut.n.valid_o
-    assert o_n_valid == val
-
-def check_o_n_valid2(dut, val):
-    o_n_valid = yield dut.n.valid_o
-    assert o_n_valid == val
-
-
-def tbench(dut):
-    #yield dut.i_p_rst.eq(1)
-    yield dut.n.ready_i.eq(0)
-    #yield dut.p.ready_o.eq(0)
-    yield
-    yield
-    #yield dut.i_p_rst.eq(0)
-    yield dut.n.ready_i.eq(1)
-    yield dut.p.data_i.eq(5)
-    yield dut.p.valid_i.eq(1)
-    yield
-
-    yield dut.p.data_i.eq(7)
-    yield from check_o_n_valid(dut, 0) # effects of i_p_valid delayed
-    yield
-    yield from check_o_n_valid(dut, 1) # ok *now* i_p_valid effect is felt
-
-    yield dut.p.data_i.eq(2)
-    yield
-    yield dut.n.ready_i.eq(0) # begin going into "stall" (next stage says ready)
-    yield dut.p.data_i.eq(9)
-    yield
-    yield dut.p.valid_i.eq(0)
-    yield dut.p.data_i.eq(12)
-    yield
-    yield dut.p.data_i.eq(32)
-    yield dut.n.ready_i.eq(1)
-    yield
-    yield from check_o_n_valid(dut, 1) # buffer still needs to output
-    yield
-    yield from check_o_n_valid(dut, 1) # buffer still needs to output
-    yield
-    yield from check_o_n_valid(dut, 0) # buffer outputted, *now* we're done.
-    yield
-
-
-def tbench2(dut):
-    #yield dut.p.i_rst.eq(1)
-    yield dut.n.ready_i.eq(0)
-    #yield dut.p.ready_o.eq(0)
-    yield
-    yield
-    #yield dut.p.i_rst.eq(0)
-    yield dut.n.ready_i.eq(1)
-    yield dut.p.data_i.eq(5)
-    yield dut.p.valid_i.eq(1)
-    yield
-
-    yield dut.p.data_i.eq(7)
-    yield from check_o_n_valid2(dut, 0) # effects of i_p_valid delayed 2 clocks
-    yield
-    yield from check_o_n_valid2(dut, 0) # effects of i_p_valid delayed 2 clocks
-
-    yield dut.p.data_i.eq(2)
-    yield
-    yield from check_o_n_valid2(dut, 1) # ok *now* i_p_valid effect is felt
-    yield dut.n.ready_i.eq(0) # begin going into "stall" (next stage says ready)
-    yield dut.p.data_i.eq(9)
-    yield
-    yield dut.p.valid_i.eq(0)
-    yield dut.p.data_i.eq(12)
-    yield
-    yield dut.p.data_i.eq(32)
-    yield dut.n.ready_i.eq(1)
-    yield
-    yield from check_o_n_valid2(dut, 1) # buffer still needs to output
-    yield
-    yield from check_o_n_valid2(dut, 1) # buffer still needs to output
-    yield
-    yield from check_o_n_valid2(dut, 1) # buffer still needs to output
-    yield
-    yield from check_o_n_valid2(dut, 0) # buffer outputted, *now* we're done.
-    yield
-    yield
-    yield
-
-
-class Test3:
-    def __init__(self, dut, resultfn):
-        self.dut = dut
-        self.resultfn = resultfn
-        self.data = []
-        for i in range(num_tests):
-            #data.append(randint(0, 1<<16-1))
-            self.data.append(i+1)
-        self.i = 0
-        self.o = 0
-
-    def send(self):
-        while self.o != len(self.data):
-            send_range = randint(0, 3)
-            for j in range(randint(1,10)):
-                if send_range == 0:
-                    send = True
-                else:
-                    send = randint(0, send_range) != 0
-                o_p_ready = yield self.dut.p.ready_o
-                if not o_p_ready:
-                    yield
-                    continue
-                if send and self.i != len(self.data):
-                    yield self.dut.p.valid_i.eq(1)
-                    yield self.dut.p.data_i.eq(self.data[self.i])
-                    self.i += 1
-                else:
-                    yield self.dut.p.valid_i.eq(0)
-                yield
-
-    def rcv(self):
-        while self.o != len(self.data):
-            stall_range = randint(0, 3)
-            for j in range(randint(1,10)):
-                stall = randint(0, stall_range) != 0
-                yield self.dut.n.ready_i.eq(stall)
-                yield
-                o_n_valid = yield self.dut.n.valid_o
-                i_n_ready = yield self.dut.n.ready_i_test
-                if not o_n_valid or not i_n_ready:
-                    continue
-                data_o = yield self.dut.n.data_o
-                self.resultfn(data_o, self.data[self.o], self.i, self.o)
-                self.o += 1
-                if self.o == len(self.data):
-                    break
-
-def resultfn_3(data_o, expected, i, o):
-    assert data_o == expected + 1, \
-                "%d-%d data %x not match %x\n" \
-                % (i, o, data_o, expected)
-
-def data_placeholder():
-        data = []
-        for i in range(num_tests):
-            d = PlaceHolder()
-            d.src1 = randint(0, 1<<16-1)
-            d.src2 = randint(0, 1<<16-1)
-            data.append(d)
-        return data
-
-def data_dict():
-        data = []
-        for i in range(num_tests):
-            data.append({'src1': randint(0, 1<<16-1),
-                         'src2': randint(0, 1<<16-1)})
-        return data
-
-
-class Test5:
-    def __init__(self, dut, resultfn, data=None, stage_ctl=False):
-        self.dut = dut
-        self.resultfn = resultfn
-        self.stage_ctl = stage_ctl
-        if data:
-            self.data = data
-        else:
-            self.data = []
-            for i in range(num_tests):
-                self.data.append((randint(0, 1<<16-1), randint(0, 1<<16-1)))
-        self.i = 0
-        self.o = 0
-
-    def send(self):
-        while self.o != len(self.data):
-            send_range = randint(0, 3)
-            for j in range(randint(1,10)):
-                if send_range == 0:
-                    send = True
-                else:
-                    send = randint(0, send_range) != 0
-                #send = True
-                o_p_ready = yield self.dut.p.ready_o
-                if not o_p_ready:
-                    yield
-                    continue
-                if send and self.i != len(self.data):
-                    yield self.dut.p.valid_i.eq(1)
-                    for v in self.dut.set_input(self.data[self.i]):
-                        yield v
-                    self.i += 1
-                else:
-                    yield self.dut.p.valid_i.eq(0)
-                yield
-
-    def rcv(self):
-        while self.o != len(self.data):
-            stall_range = randint(0, 3)
-            for j in range(randint(1,10)):
-                ready = randint(0, stall_range) != 0
-                #ready = True
-                yield self.dut.n.ready_i.eq(ready)
-                yield
-                o_n_valid = yield self.dut.n.valid_o
-                i_n_ready = yield self.dut.n.ready_i_test
-                if not o_n_valid or not i_n_ready:
-                    continue
-                if isinstance(self.dut.n.data_o, Record):
-                    data_o = {}
-                    dod = self.dut.n.data_o
-                    for k, v in dod.fields.items():
-                        data_o[k] = yield v
-                else:
-                    data_o = yield self.dut.n.data_o
-                self.resultfn(data_o, self.data[self.o], self.i, self.o)
-                self.o += 1
-                if self.o == len(self.data):
-                    break
-
-def resultfn_5(data_o, expected, i, o):
-    res = expected[0] + expected[1]
-    assert data_o == res, \
-                "%d-%d data %x not match %s\n" \
-                % (i, o, data_o, repr(expected))
-
-def tbench4(dut):
-    data = []
-    for i in range(num_tests):
-        #data.append(randint(0, 1<<16-1))
-        data.append(i+1)
-    i = 0
-    o = 0
-    while True:
-        stall = randint(0, 3) != 0
-        send = randint(0, 5) != 0
-        yield dut.n.ready_i.eq(stall)
-        o_p_ready = yield dut.p.ready_o
-        if o_p_ready:
-            if send and i != len(data):
-                yield dut.p.valid_i.eq(1)
-                yield dut.p.data_i.eq(data[i])
-                i += 1
-            else:
-                yield dut.p.valid_i.eq(0)
-        yield
-        o_n_valid = yield dut.n.valid_o
-        i_n_ready = yield dut.n.ready_i_test
-        if o_n_valid and i_n_ready:
-            data_o = yield dut.n.data_o
-            assert data_o == data[o] + 2, "%d-%d data %x not match %x\n" \
-                                        % (i, o, data_o, data[o])
-            o += 1
-            if o == len(data):
-                break
-
-######################################################################
-# Test 2 and 4
-######################################################################
-
-class ExampleBufPipe2(ControlBase):
-    """ Example of how to do chained pipeline stages.
-    """
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        pipe1 = ExampleBufPipe()
-        pipe2 = ExampleBufPipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-
-######################################################################
-# Test 9
-######################################################################
-
-class ExampleBufPipeChain2(BufferedHandshake):
-    """ connects two stages together as a *single* combinatorial stage.
-    """
-    def __init__(self):
-        stage1 = ExampleStageCls()
-        stage2 = ExampleStageCls()
-        combined = StageChain([stage1, stage2])
-        BufferedHandshake.__init__(self, combined)
-
-
-def data_chain2():
-        data = []
-        for i in range(num_tests):
-            data.append(randint(0, 1<<16-2))
-        return data
-
-
-def resultfn_9(data_o, expected, i, o):
-    res = expected + 2
-    assert data_o == res, \
-                "%d-%d received data %x not match expected %x\n" \
-                % (i, o, data_o, res)
-
-
-######################################################################
-# Test 6 and 10
-######################################################################
-
-class SetLessThan(Elaboratable):
-    def __init__(self, width, signed):
-        self.m = Module()
-        self.src1 = Signal((width, signed), name="src1")
-        self.src2 = Signal((width, signed), name="src2")
-        self.output = Signal(width, name="out")
-
-    def elaborate(self, platform):
-        self.m.d.comb += self.output.eq(Mux(self.src1 < self.src2, 1, 0))
-        return self.m
-
-
-class LTStage(StageCls):
-    """ module-based stage example
-    """
-    def __init__(self):
-        self.slt = SetLessThan(16, True)
-
-    def ispec(self, name):
-        return (Signal(16, name="%s_sig1" % name),
-                Signal(16, name="%s_sig2" % name))
-
-    def ospec(self, name):
-        return Signal(16, "%s_out" % name)
-
-    def setup(self, m, i):
-        self.o = Signal(16)
-        m.submodules.slt = self.slt
-        m.d.comb += self.slt.src1.eq(i[0])
-        m.d.comb += self.slt.src2.eq(i[1])
-        m.d.comb += self.o.eq(self.slt.output)
-
-    def process(self, i):
-        return self.o
-
-
-class LTStageDerived(SetLessThan, StageCls):
-    """ special version of a nmigen module where the module is also a stage
-
-        shows that you don't actually need to combinatorially connect
-        to the outputs, or add the module as a submodule: just return
-        the module output parameter(s) from the Stage.process() function
-    """
-
-    def __init__(self):
-        SetLessThan.__init__(self, 16, True)
-
-    def ispec(self):
-        return (Signal(16), Signal(16))
-
-    def ospec(self):
-        return Signal(16)
-
-    def setup(self, m, i):
-        m.submodules.slt = self
-        m.d.comb += self.src1.eq(i[0])
-        m.d.comb += self.src2.eq(i[1])
-
-    def process(self, i):
-        return self.output
-
-
-class ExampleLTPipeline(UnbufferedPipeline):
-    """ an example of how to use the unbuffered pipeline.
-    """
-
-    def __init__(self):
-        stage = LTStage()
-        UnbufferedPipeline.__init__(self, stage)
-
-
-class ExampleLTBufferedPipeDerived(BufferedHandshake):
-    """ an example of how to use the buffered pipeline.
-    """
-
-    def __init__(self):
-        stage = LTStageDerived()
-        BufferedHandshake.__init__(self, stage)
-
-
-def resultfn_6(data_o, expected, i, o):
-    res = 1 if expected[0] < expected[1] else 0
-    assert data_o == res, \
-                "%d-%d data %x not match %s\n" \
-                % (i, o, data_o, repr(expected))
-
-
-######################################################################
-# Test 7
-######################################################################
-
-class ExampleAddRecordStage(StageCls):
-    """ example use of a Record
-    """
-
-    record_spec = [('src1', 16), ('src2', 16)]
-    def ispec(self):
-        """ returns a Record using the specification
-        """
-        return Record(self.record_spec)
-
-    def ospec(self):
-        return Record(self.record_spec)
-
-    def process(self, i):
-        """ process the input data, returning a dictionary with key names
-            that exactly match the Record's attributes.
-        """
-        return {'src1': i.src1 + 1,
-                'src2': i.src2 + 1}
-
-######################################################################
-# Test 11
-######################################################################
-
-class ExampleAddRecordPlaceHolderStage(StageCls):
-    """ example use of a Record, with a placeholder as the processing result
-    """
-
-    record_spec = [('src1', 16), ('src2', 16)]
-    def ispec(self):
-        """ returns a Record using the specification
-        """
-        return Record(self.record_spec)
-
-    def ospec(self):
-        return Record(self.record_spec)
-
-    def process(self, i):
-        """ process the input data, returning a PlaceHolder class instance
-            with attributes that exactly match those of the Record.
-        """
-        o = PlaceHolder()
-        o.src1 = i.src1 + 1
-        o.src2 = i.src2 + 1
-        return o
-
-
-# a dummy class that may have stuff assigned to instances once created
-class PlaceHolder: pass
-
-
-class ExampleAddRecordPipe(UnbufferedPipeline):
-    """ an example of how to use the combinatorial pipeline.
-    """
-
-    def __init__(self):
-        stage = ExampleAddRecordStage()
-        UnbufferedPipeline.__init__(self, stage)
-
-
-def resultfn_7(data_o, expected, i, o):
-    res = (expected['src1'] + 1, expected['src2'] + 1)
-    assert data_o['src1'] == res[0] and data_o['src2'] == res[1], \
-                "%d-%d data %s not match %s\n" \
-                % (i, o, repr(data_o), repr(expected))
-
-
-class ExampleAddRecordPlaceHolderPipe(UnbufferedPipeline):
-    """ an example of how to use the combinatorial pipeline.
-    """
-
-    def __init__(self):
-        stage = ExampleAddRecordPlaceHolderStage()
-        UnbufferedPipeline.__init__(self, stage)
-
-
-def resultfn_test11(data_o, expected, i, o):
-    res1 = expected.src1 + 1
-    res2 = expected.src2 + 1
-    assert data_o['src1'] == res1 and data_o['src2'] == res2, \
-                "%d-%d data %s not match %s\n" \
-                % (i, o, repr(data_o), repr(expected))
-
-
-######################################################################
-# Test 8
-######################################################################
-
-
-class Example2OpClass:
-    """ an example of a class used to store 2 operands.
-        requires an eq function, to conform with the pipeline stage API
-    """
-
-    def __init__(self):
-        self.op1 = Signal(16)
-        self.op2 = Signal(16)
-
-    def eq(self, i):
-        return [self.op1.eq(i.op1), self.op2.eq(i.op2)]
-
-
-class ExampleAddClassStage(StageCls):
-    """ an example of how to use the buffered pipeline, as a class instance
-    """
-
-    def ispec(self):
-        """ returns an instance of an Example2OpClass.
-        """
-        return Example2OpClass()
-
-    def ospec(self):
-        """ returns an output signal which will happen to contain the sum
-            of the two inputs
-        """
-        return Signal(16, name="add2_out")
-
-    def process(self, i):
-        """ process the input data (sums the values in the tuple) and returns it
-        """
-        return i.op1 + i.op2
-
-
-class ExampleBufPipeAddClass(BufferedHandshake):
-    """ an example of how to use the buffered pipeline, using a class instance
-    """
-
-    def __init__(self):
-        addstage = ExampleAddClassStage()
-        BufferedHandshake.__init__(self, addstage)
-
-
-class TestInputAdd:
-    """ the eq function, called by set_input, needs an incoming object
-        that conforms to the Example2OpClass.eq function requirements
-        easiest way to do that is to create a class that has the exact
-        same member layout (self.op1, self.op2) as Example2OpClass
-    """
-    def __init__(self, op1, op2):
-        self.op1 = op1
-        self.op2 = op2
-
-
-def resultfn_8(data_o, expected, i, o):
-    res = expected.op1 + expected.op2 # these are a TestInputAdd instance
-    assert data_o == res, \
-                "%d-%d data %s res %x not match %s\n" \
-                % (i, o, repr(data_o), res, repr(expected))
-
-def data_2op():
-        data = []
-        for i in range(num_tests):
-            data.append(TestInputAdd(randint(0, 1<<16-1), randint(0, 1<<16-1)))
-        return data
-
-
-######################################################################
-# Test 12
-######################################################################
-
-class ExampleStageDelayCls(StageCls, Elaboratable):
-    """ an example of how to use the buffered pipeline, in a static class
-        fashion
-    """
-
-    def __init__(self, valid_trigger=2):
-        self.count = Signal(2)
-        self.valid_trigger = valid_trigger
-
-    def ispec(self):
-        return Signal(16, name="example_input_signal")
-
-    def ospec(self):
-        return Signal(16, name="example_output_signal")
-
-    @property
-    def d_ready(self):
-        """ data is ready to be accepted when this is true
-        """
-        return (self.count == 1)# | (self.count == 3)
-        return Const(1)
-
-    def d_valid(self, ready_i):
-        """ data is valid at output when this is true
-        """
-        return self.count == self.valid_trigger
-        return Const(1)
-
-    def process(self, i):
-        """ process the input data and returns it (adds 1)
-        """
-        return i + 1
-
-    def elaborate(self, platform):
-        m = Module()
-        m.d.sync += self.count.eq(self.count + 1)
-        return m
-
-
-class ExampleBufDelayedPipe(BufferedHandshake):
-
-    def __init__(self):
-        stage = ExampleStageDelayCls(valid_trigger=2)
-        BufferedHandshake.__init__(self, stage, stage_ctl=True)
-
-    def elaborate(self, platform):
-        m = BufferedHandshake.elaborate(self, platform)
-        m.submodules.stage = self.stage
-        return m
-
-
-def data_chain1():
-        data = []
-        for i in range(num_tests):
-            data.append(1<<((i*3)%15))
-            #data.append(randint(0, 1<<16-2))
-            #print (hex(data[-1]))
-        return data
-
-
-def resultfn_12(data_o, expected, i, o):
-    res = expected + 1
-    assert data_o == res, \
-                "%d-%d data %x not match %x\n" \
-                % (i, o, data_o, res)
-
-
-######################################################################
-# Test 13
-######################################################################
-
-class ExampleUnBufDelayedPipe(BufferedHandshake):
-
-    def __init__(self):
-        stage = ExampleStageDelayCls(valid_trigger=3)
-        BufferedHandshake.__init__(self, stage, stage_ctl=True)
-
-    def elaborate(self, platform):
-        m = BufferedHandshake.elaborate(self, platform)
-        m.submodules.stage = self.stage
-        return m
-
-######################################################################
-# Test 15
-######################################################################
-
-class ExampleBufModeAdd1Pipe(SimpleHandshake):
-
-    def __init__(self):
-        stage = ExampleStageCls()
-        SimpleHandshake.__init__(self, stage)
-
-
-######################################################################
-# Test 16
-######################################################################
-
-class ExampleBufModeUnBufPipe(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        pipe1 = ExampleBufModeAdd1Pipe()
-        pipe2 = ExampleBufAdd1Pipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-######################################################################
-# Test 17
-######################################################################
-
-class ExampleUnBufAdd1Pipe2(UnbufferedPipeline2):
-
-    def __init__(self):
-        stage = ExampleStageCls()
-        UnbufferedPipeline2.__init__(self, stage)
-
-
-######################################################################
-# Test 18
-######################################################################
-
-class PassThroughTest(PassThroughHandshake):
-
-    def iospecfn(self):
-        return Signal(16, "out")
-
-    def __init__(self):
-        stage = PassThroughStage(self.iospecfn)
-        PassThroughHandshake.__init__(self, stage)
-
-def resultfn_identical(data_o, expected, i, o):
-    res = expected
-    assert data_o == res, \
-                "%d-%d data %x not match %x\n" \
-                % (i, o, data_o, res)
-
-
-######################################################################
-# Test 19
-######################################################################
-
-class ExamplePassAdd1Pipe(PassThroughHandshake):
-
-    def __init__(self):
-        stage = ExampleStageCls()
-        PassThroughHandshake.__init__(self, stage)
-
-
-class ExampleBufPassThruPipe(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        # XXX currently fails: any other permutation works fine.
-        # p1=u,p2=b ok p1=u,p2=u ok p1=b,p2=b ok
-        # also fails using UnbufferedPipeline as well
-        pipe1 = ExampleBufModeAdd1Pipe()
-        pipe2 = ExamplePassAdd1Pipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-
-######################################################################
-# Test 20
-######################################################################
-
-def iospecfn():
-    return Signal(16, name="d_in")
-
-class FIFOTest16(FIFOControl):
-
-    def __init__(self):
-        stage = PassThroughStage(iospecfn)
-        FIFOControl.__init__(self, 2, stage)
-
-
-######################################################################
-# Test 21
-######################################################################
-
-class ExampleFIFOPassThruPipe1(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        pipe1 = FIFOTest16()
-        pipe2 = FIFOTest16()
-        pipe3 = ExamplePassAdd1Pipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-        m.submodules.pipe3 = pipe3
-
-        m.d.comb += self.connect([pipe1, pipe2, pipe3])
-
-        return m
-
-
-######################################################################
-# Test 22
-######################################################################
-
-class Example2OpRecord(RecordObject):
-    def __init__(self):
-        RecordObject.__init__(self)
-        self.op1 = Signal(16)
-        self.op2 = Signal(16)
-
-
-class ExampleAddRecordObjectStage(StageCls):
-
-    def ispec(self):
-        """ returns an instance of an Example2OpRecord.
-        """
-        return Example2OpRecord()
-
-    def ospec(self):
-        """ returns an output signal which will happen to contain the sum
-            of the two inputs
-        """
-        return Signal(16)
-
-    def process(self, i):
-        """ process the input data (sums the values in the tuple) and returns it
-        """
-        return i.op1 + i.op2
-
-
-class ExampleRecordHandshakeAddClass(SimpleHandshake):
-
-    def __init__(self):
-        addstage = ExampleAddRecordObjectStage()
-        SimpleHandshake.__init__(self, stage=addstage)
-
-
-######################################################################
-# Test 23
-######################################################################
-
-def iospecfnrecord():
-    return Example2OpRecord()
-
-class FIFOTestRecordControl(FIFOControl):
-
-    def __init__(self):
-        stage = PassThroughStage(iospecfnrecord)
-        FIFOControl.__init__(self, 2, stage)
-
-
-class ExampleFIFORecordObjectPipe(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        pipe1 = FIFOTestRecordControl()
-        pipe2 = ExampleRecordHandshakeAddClass()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-
-######################################################################
-# Test 24
-######################################################################
-
-class FIFOTestRecordAddStageControl(FIFOControl):
-
-    def __init__(self):
-        stage = ExampleAddRecordObjectStage()
-        FIFOControl.__init__(self, 2, stage)
-
-
-
-######################################################################
-# Test 25
-######################################################################
-
-class FIFOTestAdd16(FIFOControl):
-
-    def __init__(self):
-        stage = ExampleStageCls()
-        FIFOControl.__init__(self, 2, stage)
-
-
-class ExampleFIFOAdd2Pipe(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        pipe1 = FIFOTestAdd16()
-        pipe2 = FIFOTestAdd16()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-
-######################################################################
-# Test 26
-######################################################################
-
-def iospecfn24():
-    return (Signal(16, name="src1"), Signal(16, name="src2"))
-
-class FIFOTest2x16(FIFOControl):
-
-    def __init__(self):
-        stage = PassThroughStage(iospecfn2)
-        FIFOControl.__init__(self, 2, stage)
-
-
-######################################################################
-# Test 997
-######################################################################
-
-class ExampleBufPassThruPipe2(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        # XXX currently fails: any other permutation works fine.
-        # p1=u,p2=b ok p1=u,p2=u ok p1=b,p2=b ok
-        # also fails using UnbufferedPipeline as well
-        #pipe1 = ExampleUnBufAdd1Pipe()
-        #pipe2 = ExampleBufAdd1Pipe()
-        pipe1 = ExampleBufAdd1Pipe()
-        pipe2 = ExamplePassAdd1Pipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-
-######################################################################
-# Test 998
-######################################################################
-
-class ExampleBufPipe3(ControlBase):
-    """ Example of how to do delayed pipeline, where the stage signals
-        whether it is ready.
-    """
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        pipe1 = ExampleBufDelayedPipe()
-        pipe2 = ExampleBufPipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-######################################################################
-# Test 999 - XXX FAILS
-# http://bugs.libre-riscv.org/show_bug.cgi?id=57
-######################################################################
-
-class ExampleBufAdd1Pipe(BufferedHandshake):
-
-    def __init__(self):
-        stage = ExampleStageCls()
-        BufferedHandshake.__init__(self, stage)
-
-
-class ExampleUnBufAdd1Pipe(UnbufferedPipeline):
-
-    def __init__(self):
-        stage = ExampleStageCls()
-        UnbufferedPipeline.__init__(self, stage)
-
-
-class ExampleBufUnBufPipe(ControlBase):
-
-    def elaborate(self, platform):
-        m = ControlBase.elaborate(self, platform)
-
-        # XXX currently fails: any other permutation works fine.
-        # p1=u,p2=b ok p1=u,p2=u ok p1=b,p2=b ok
-        # also fails using UnbufferedPipeline as well
-        #pipe1 = ExampleUnBufAdd1Pipe()
-        #pipe2 = ExampleBufAdd1Pipe()
-        pipe1 = ExampleBufAdd1Pipe()
-        pipe2 = ExampleUnBufAdd1Pipe()
-
-        m.submodules.pipe1 = pipe1
-        m.submodules.pipe2 = pipe2
-
-        m.d.comb += self.connect([pipe1, pipe2])
-
-        return m
-
-
-######################################################################
-# Unit Tests
-######################################################################
-
-num_tests = 10
-
-if __name__ == '__main__':
-    if False:
-        print ("test 1")
-        dut = ExampleBufPipe()
-        run_simulation(dut, tbench(dut), vcd_name="test_bufpipe.vcd")
-
-        print ("test 2")
-        dut = ExampleBufPipe2()
-        run_simulation(dut, tbench2(dut), vcd_name="test_bufpipe2.vcd")
-        ports = [dut.p.valid_i, dut.n.ready_i,
-                 dut.n.valid_o, dut.p.ready_o] + \
-                 [dut.p.data_i] + [dut.n.data_o]
-        vl = rtlil.convert(dut, ports=ports)
-        with open("test_bufpipe2.il", "w") as f:
-            f.write(vl)
-
-
-    print ("test 3")
-    dut = ExampleBufPipe()
-    test = Test3(dut, resultfn_3)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe3.vcd")
-
-    print ("test 3.5")
-    dut = ExamplePipeline()
-    test = Test3(dut, resultfn_3)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_combpipe3.vcd")
-
-    print ("test 4")
-    dut = ExampleBufPipe2()
-    run_simulation(dut, tbench4(dut), vcd_name="test_bufpipe4.vcd")
-
-    print ("test 5")
-    dut = ExampleBufPipeAdd()
-    test = Test5(dut, resultfn_5, stage_ctl=True)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe5.vcd")
-
-    print ("test 6")
-    dut = ExampleLTPipeline()
-    test = Test5(dut, resultfn_6)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltcomb6.vcd")
-
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             list(dut.p.data_i) + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_ltcomb_pipe.il", "w") as f:
-        f.write(vl)
-
-    print ("test 7")
-    dut = ExampleAddRecordPipe()
-    data=data_dict()
-    test = Test5(dut, resultfn_7, data=data)
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o,
-             dut.p.data_i.src1, dut.p.data_i.src2,
-             dut.n.data_o.src1, dut.n.data_o.src2]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_recordcomb_pipe.il", "w") as f:
-        f.write(vl)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
-
-    print ("test 8")
-    dut = ExampleBufPipeAddClass()
-    data=data_2op()
-    test = Test5(dut, resultfn_8, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe8.vcd")
-
-    print ("test 9")
-    dut = ExampleBufPipeChain2()
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufpipechain2.il", "w") as f:
-        f.write(vl)
-
-    data = data_chain2()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv],
-                        vcd_name="test_bufpipechain2.vcd")
-
-    print ("test 10")
-    dut = ExampleLTBufferedPipeDerived()
-    test = Test5(dut, resultfn_6)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltbufpipe10.vcd")
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_ltbufpipe10.il", "w") as f:
-        f.write(vl)
-
-    print ("test 11")
-    dut = ExampleAddRecordPlaceHolderPipe()
-    data=data_placeholder()
-    test = Test5(dut, resultfn_test11, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
-
-
-    print ("test 12")
-    dut = ExampleBufDelayedPipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_12, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe12.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufpipe12.il", "w") as f:
-        f.write(vl)
-
-    print ("test 13")
-    dut = ExampleUnBufDelayedPipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_12, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_unbufpipe13.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_unbufpipe13.il", "w") as f:
-        f.write(vl)
-
-    print ("test 15")
-    dut = ExampleBufModeAdd1Pipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_12, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufunbuf15.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufunbuf15.il", "w") as f:
-        f.write(vl)
-
-    print ("test 16")
-    dut = ExampleBufModeUnBufPipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufunbuf16.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufunbuf16.il", "w") as f:
-        f.write(vl)
-
-    print ("test 17")
-    dut = ExampleUnBufAdd1Pipe2()
-    data = data_chain1()
-    test = Test5(dut, resultfn_12, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_unbufpipe17.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_unbufpipe17.il", "w") as f:
-        f.write(vl)
-
-    print ("test 18")
-    dut = PassThroughTest()
-    data = data_chain1()
-    test = Test5(dut, resultfn_identical, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_passthru18.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_passthru18.il", "w") as f:
-        f.write(vl)
-
-    print ("test 19")
-    dut = ExampleBufPassThruPipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpass19.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufpass19.il", "w") as f:
-        f.write(vl)
-
-    print ("test 20")
-    dut = FIFOTest16()
-    data = data_chain1()
-    test = Test5(dut, resultfn_identical, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_fifo20.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_fifo20.il", "w") as f:
-        f.write(vl)
-
-    print ("test 21")
-    dut = ExampleFIFOPassThruPipe1()
-    data = data_chain1()
-    test = Test5(dut, resultfn_12, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_fifopass21.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_fifopass21.il", "w") as f:
-        f.write(vl)
-
-    print ("test 22")
-    dut = ExampleRecordHandshakeAddClass()
-    data=data_2op()
-    test = Test5(dut, resultfn_8, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord22.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i.op1, dut.p.data_i.op2] + \
-             [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_addrecord22.il", "w") as f:
-        f.write(vl)
-
-    print ("test 23")
-    dut = ExampleFIFORecordObjectPipe()
-    data=data_2op()
-    test = Test5(dut, resultfn_8, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord23.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i.op1, dut.p.data_i.op2] + \
-             [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_addrecord23.il", "w") as f:
-        f.write(vl)
-
-    print ("test 24")
-    dut = FIFOTestRecordAddStageControl()
-    data=data_2op()
-    test = Test5(dut, resultfn_8, data=data)
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i.op1, dut.p.data_i.op2] + \
-             [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_addrecord24.il", "w") as f:
-        f.write(vl)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord24.vcd")
-
-    print ("test 25")
-    dut = ExampleFIFOAdd2Pipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_add2pipe25.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_add2pipe25.il", "w") as f:
-        f.write(vl)
-
-    print ("test 997")
-    dut = ExampleBufPassThruPipe2()
-    data = data_chain1()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpass997.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufpass997.il", "w") as f:
-        f.write(vl)
-
-    print ("test 998 (fails, bug)")
-    dut = ExampleBufPipe3()
-    data = data_chain1()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe14.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufpipe14.il", "w") as f:
-        f.write(vl)
-
-    print ("test 999 (expected to fail, which is a bug)")
-    dut = ExampleBufUnBufPipe()
-    data = data_chain1()
-    test = Test5(dut, resultfn_9, data=data)
-    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufunbuf999.vcd")
-    ports = [dut.p.valid_i, dut.n.ready_i,
-             dut.n.valid_o, dut.p.ready_o] + \
-             [dut.p.data_i] + [dut.n.data_o]
-    vl = rtlil.convert(dut, ports=ports)
-    with open("test_bufunbuf999.il", "w") as f:
-        f.write(vl)
-
index 17cee24ebb4cb5e85cd49311140017912f4488df..1a338f56f85dbac45e0eb15bede5bdf701500f6c 100644 (file)
@@ -8,8 +8,8 @@ from nmigen.compat.sim import run_simulation
 
 
 from fpbase import FPNumIn, FPNumOut, FPOpIn, FPOpOut, FPBase, FPState
 
 
 from fpbase import FPNumIn, FPNumOut, FPOpIn, FPOpOut, FPBase, FPState
-from nmoperator import eq
-from singlepipe import SimpleHandshake, ControlBase
+from nmutil.nmoperator import eq
+from nmutil.singlepipe import SimpleHandshake, ControlBase
 from test_buf_pipe import data_chain2, Test5
 
 
 from test_buf_pipe import data_chain2, Test5
 
 
index 35abe2eaf46b3aa148826b6821640cf39dfe2786..221ece1db7f4b51ab7269f08c915b2ebba363fa1 100644 (file)
@@ -11,9 +11,9 @@ from nmigen import Module, Signal, Cat, Value, Elaboratable
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 
-from multipipe import CombMultiOutPipeline, CombMuxOutPipe
-from multipipe import PriorityCombMuxInPipe
-from singlepipe import SimpleHandshake, RecordObject, Object
+from nmutil.multipipe import CombMultiOutPipeline, CombMuxOutPipe
+from nmutil.multipipe import PriorityCombMuxInPipe
+from nmutil.singlepipe import SimpleHandshake, RecordObject, Object
 
 
 class PassData2(RecordObject):
 
 
 class PassData2(RecordObject):
index b674a87069f82394ad771a5f658d8ee0838721e5..768d9d9a09e6efb1e76c2217e27d2e5192c8778e 100644 (file)
@@ -4,8 +4,8 @@ from nmigen import Module, Signal, Cat, Elaboratable
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 
-from multipipe import CombMuxOutPipe
-from singlepipe import SimpleHandshake, PassThroughHandshake, RecordObject
+from nmutil.multipipe import CombMuxOutPipe
+from nmutil.singlepipe import SimpleHandshake, PassThroughHandshake, RecordObject
 
 
 class PassInData(RecordObject):
 
 
 class PassInData(RecordObject):
index 5f7891e881271fad4ba5f730d8d9070c29e38024..ca7181d0df4d99567873998d09dc444cb57f740c 100644 (file)
@@ -4,8 +4,8 @@ from nmigen import Module, Signal, Cat
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 
-from singlepipe import PassThroughStage
-from multipipe import (CombMultiInPipeline, PriorityCombMuxInPipe)
+from nmutil.singlepipe import PassThroughStage
+from nmutil.multipipe import (CombMultiInPipeline, PriorityCombMuxInPipe)
 
 
 class PassData:
 
 
 class PassData:
index 62452c37c8478dba1dfa030da619a8409322672c..b373f1e3d79347566da70d5d7b149806cc97c50f 100644 (file)
@@ -5,7 +5,7 @@
 from nmigen import Module
 from nmigen.cli import main, verilog
 
 from nmigen import Module
 from nmigen.cli import main, verilog
 
-from singlepipe import (StageChain, SimpleHandshake,
+from nmutil.singlepipe import (StageChain, SimpleHandshake,
                         PassThroughStage)
 
 from fpbase import FPState
                         PassThroughStage)
 
 from fpbase import FPState
index 5c4eed18d9aba94a9956c425e216e1ba579192dd..eea893552047d772f8ba7adf930db3a6e729e118 100644 (file)
@@ -5,9 +5,9 @@
 from nmigen import Module
 from nmigen.cli import main, verilog
 
 from nmigen import Module
 from nmigen.cli import main, verilog
 
-from singlepipe import (ControlBase, SimpleHandshake, PassThroughStage)
-from multipipe import CombMuxOutPipe
-from multipipe import PriorityCombMuxInPipe
+from nmutil.singlepipe import (ControlBase, SimpleHandshake, PassThroughStage)
+from nmutil.multipipe import CombMuxOutPipe
+from nmutil.multipipe import PriorityCombMuxInPipe
 
 from ieee754.fpcommon.getop import FPADDBaseData
 from ieee754.fpcommon.denorm import FPSCData
 
 from ieee754.fpcommon.getop import FPADDBaseData
 from ieee754.fpcommon.denorm import FPSCData
index 39254a74f772d4565439d975c0857b4c7d02bd19..978851ef0c99a6aa8829c8c05aadd118cb04bb21 100644 (file)
@@ -7,7 +7,7 @@ from nmigen.cli import main, verilog
 from math import log
 
 from fpbase import FPNumDecode
 from math import log
 
 from fpbase import FPNumDecode
-from singlepipe import SimpleHandshake, StageChain
+from nmutil.singlepipe import SimpleHandshake, StageChain
 
 from fpbase import FPState, FPID
 from ieee754.fpcommon.getop import FPADDBaseData
 
 from fpbase import FPState, FPID
 from ieee754.fpcommon.getop import FPADDBaseData
index e7f298d3e433eb74c3104625e9d4698066668d17..5afe702e707a6ef74f732c7d94f4c8fe8510a56b 100644 (file)
@@ -8,7 +8,7 @@ from math import log
 
 from fpbase import FPOpIn, FPOpOut
 from fpbase import Trigger
 
 from fpbase import FPOpIn, FPOpOut
 from fpbase import Trigger
-from singlepipe import (StageChain, SimpleHandshake)
+from nmutil.singlepipe import (StageChain, SimpleHandshake)
 
 from fpbase import FPState, FPID
 from ieee754.fpcommon.getop import (FPGetOp, FPADDBaseData, FPGet2Op)
 
 from fpbase import FPState, FPID
 from ieee754.fpcommon.getop import (FPGetOp, FPADDBaseData, FPGet2Op)
index 1988997a8948abc7bad5b0c1276736c0e2fd3dc8..f772d9041b866a11f5e06b9ab80cca31228a790f 100644 (file)
@@ -9,13 +9,13 @@ from math import log
 
 from fpbase import FPNumIn, FPNumOut, FPOpIn, Overflow, FPBase, FPNumBase
 from fpbase import MultiShiftRMerge, Trigger
 
 from fpbase import FPNumIn, FPNumOut, FPOpIn, Overflow, FPBase, FPNumBase
 from fpbase import MultiShiftRMerge, Trigger
-from singlepipe import (ControlBase, StageChain, SimpleHandshake,
+from nmutil.singlepipe import (ControlBase, StageChain, SimpleHandshake,
                         PassThroughStage, PrevControl)
                         PassThroughStage, PrevControl)
-from multipipe import CombMuxOutPipe
-from multipipe import PriorityCombMuxInPipe
+from nmutil.multipipe import CombMuxOutPipe
+from nmutil.multipipe import PriorityCombMuxInPipe
 
 from fpbase import FPState
 
 from fpbase import FPState
-import nmoperator
+from nmutil import nmoperator
 
 
 class FPGetOpMod(Elaboratable):
 
 
 class FPGetOpMod(Elaboratable):
index 7d871f42efe73611a7c47348721e9e1fad07ea8c..ac97bf1cae5225a8b0f1a84f204c69fd92555696 100644 (file)
@@ -4,7 +4,7 @@
 
 #from nmigen.cli import main, verilog
 
 
 #from nmigen.cli import main, verilog
 
-from singlepipe import StageChain, SimpleHandshake
+from nmutil.singlepipe import StageChain, SimpleHandshake
 
 from fpbase import FPState, FPID
 from .postcalc import FPAddStage1Data
 
 from fpbase import FPState, FPID
 from .postcalc import FPAddStage1Data
index 18d6921da075d5d0d8aee6739201292928aa5d0d..7407cfb6578f0135c9d2d8d13e7d4c1dad052b68 100644 (file)
@@ -8,7 +8,7 @@ from nmigen.cli import main, verilog
 from fpbase import FPNumOut
 from fpbase import FPState
 from .roundz import FPRoundData
 from fpbase import FPNumOut
 from fpbase import FPState
 from .roundz import FPRoundData
-from singlepipe import Object
+from nmutil.singlepipe import Object
 
 
 class FPPackData(Object):
 
 
 class FPPackData(Object):
diff --git a/src/nmutil/iocontrol.py b/src/nmutil/iocontrol.py
new file mode 100644 (file)
index 0000000..0e2810b
--- /dev/null
@@ -0,0 +1,306 @@
+""" IO Control API
+
+    Associated development bugs:
+    * http://bugs.libre-riscv.org/show_bug.cgi?id=64
+    * http://bugs.libre-riscv.org/show_bug.cgi?id=57
+
+    Stage API:
+    ---------
+
+    stage requires compliance with a strict API that may be
+    implemented in several means, including as a static class.
+
+    Stages do not HOLD data, and they definitely do not contain
+    signalling (ready/valid).  They do however specify the FORMAT
+    of the incoming and outgoing data, and they provide a means to
+    PROCESS that data (from incoming format to outgoing format).
+
+    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.  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 worked examples.
+
+    * ospec() - Output data format specification.
+                format requirements identical to ispec.
+
+    * process(m, i) - Optional function for processing ispec-formatted data.
+                returns a combinatorial block of a result that
+                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
+                for adding those submodules to the module (m).
+                the submodules must be combinatorial blocks and
+                must have their inputs and output linked combinatorially.
+
+    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.  See Liskov Substitution Principle:
+    https://en.wikipedia.org/wiki/Liskov_substitution_principle
+
+    StageChain:
+    ----------
+
+    A useful combinatorial wrapper around stages that chains them together
+    and then presents a Stage-API-conformant interface.  By presenting
+    the same API as the stages it wraps, it can clearly be used recursively.
+
+    ControlBase:
+    -----------
+
+    The base class for pipelines.  Contains previous and next ready/valid/data.
+    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, Module, Value, Elaboratable
+from nmigen.cli import verilog, rtlil
+from nmigen.hdl.rec import Record
+
+from collections.abc import Sequence, Iterable
+from collections import OrderedDict
+
+from nmutil import nmoperator
+
+
+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():  # OrderedDict so order is preserved
+            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): # being called "keys" would be much better
+        return list(self)
+
+
+class RecordObject(Record):
+    def __init__(self, layout=None, name=None):
+        Record.__init__(self, layout=layout or [], name=None)
+
+    def __setattr__(self, k, v):
+        #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)}
+        elif isinstance(v, Value):
+            newlayout = {k: (k, v.shape())}
+        else:
+            newlayout = {k: (k, nmoperator.shape(v))}
+        self.layout.fields.update(newlayout)
+
+    def __iter__(self):
+        for x in self.fields.values(): # remember: fields is an OrderedDict
+            if isinstance(x, Iterable):
+                yield from x           # a bit like flatten (nmigen.tools)
+            else:
+                yield x
+
+    def ports(self): # would be better being called "keys"
+        return list(self)
+
+
+class PrevControl(Elaboratable):
+    """ contains signals that come *from* the previous stage (both in and out)
+        * valid_i: 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".
+        * ready_o: output to next stage indicating readiness to accept data
+        * 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.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.trigger = Signal(reset_less=True)
+
+    @property
+    def ready_o(self):
+        """ public-facing API: indicates (externally) that stage is ready
+        """
+        if self.stage_ctl:
+            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, fn=None, do_data=True):
+        """ internal helper function to connect stage to an input source.
+            do not use to connect stage-to-stage!
+        """
+        valid_i = prev.valid_i if direct else prev.valid_i_test
+        res = [self.valid_i.eq(valid_i),
+               prev.ready_o.eq(self.ready_o)]
+        if do_data is False:
+            return res
+        data_i = fn(prev.data_i) if fn is not None else prev.data_i
+        return res + [nmoperator.eq(self.data_i, data_i)]
+
+    @property
+    def valid_i_test(self):
+        vlen = len(self.valid_i)
+        if vlen > 1:
+            # multi-bit case: valid only when valid_i is all 1s
+            all1s = Const(-1, (len(self.valid_i), False))
+            valid_i = (self.valid_i == all1s)
+        else:
+            # single-bit valid_i case
+            valid_i = self.valid_i
+
+        # when stage indicates not ready, incoming data
+        # must "appear" to be not ready too
+        if self.stage_ctl:
+            valid_i = valid_i & self.s_ready_o
+
+        return valid_i
+
+    def elaborate(self, platform):
+        m = Module()
+        m.d.comb += self.trigger.eq(self.valid_i_test & self.ready_o)
+        return m
+
+    def eq(self, i):
+        return [nmoperator.eq(self.data_i, i.data_i),
+                self.ready_o.eq(i.ready_o),
+                self.valid_i.eq(i.valid_i)]
+
+    def __iter__(self):
+        yield self.valid_i
+        yield self.ready_o
+        if hasattr(self.data_i, "ports"):
+            yield from self.data_i.ports()
+        elif isinstance(self.data_i, Sequence):
+            yield from self.data_i
+        else:
+            yield self.data_i
+
+    def ports(self):
+        return list(self)
+
+
+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 - MUST be added by the USER of this class
+    """
+    def __init__(self, stage_ctl=False):
+        self.stage_ctl = stage_ctl
+        self.valid_o = Signal(name="n_valid_o") # self out>>  next
+        self.ready_i = Signal(name="n_ready_i") # self <<in   next
+        self.data_o = 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 ready_i_test(self):
+        if self.stage_ctl:
+            return self.ready_i & self.d_valid
+        return self.ready_i
+
+    def connect_to_next(self, nxt, do_data=True):
+        """ helper function to connect to the next stage data/valid/ready.
+            data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
+            use this when connecting stage-to-stage
+        """
+        res = [nxt.valid_i.eq(self.valid_o),
+               self.ready_i.eq(nxt.ready_o)]
+        if do_data:
+            res.append(nmoperator.eq(nxt.data_i, self.data_o))
+        return res
+
+    def _connect_out(self, nxt, direct=False, fn=None, do_data=True):
+        """ internal helper function to connect stage to an output source.
+            do not use to connect stage-to-stage!
+        """
+        ready_i = nxt.ready_i if direct else nxt.ready_i_test
+        res = [nxt.valid_o.eq(self.valid_o),
+               self.ready_i.eq(ready_i)]
+        if not do_data:
+            return res
+        data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
+        return res + [nmoperator.eq(data_o, self.data_o)]
+
+    def elaborate(self, platform):
+        m = Module()
+        m.d.comb += self.trigger.eq(self.ready_i_test & self.valid_o)
+        return m
+
+    def __iter__(self):
+        yield self.ready_i
+        yield self.valid_o
+        if hasattr(self.data_o, "ports"):
+            yield from self.data_o.ports()
+        elif isinstance(self.data_o, Sequence):
+            yield from self.data_o
+        else:
+            yield self.data_o
+
+    def ports(self):
+        return list(self)
+
index e24703f8fcbea1125c00c1a335ca2bb1290c32aa..04ab6f7e5ecaab7fa6246ecc41e18dcbfad77c85 100644 (file)
@@ -15,7 +15,7 @@ from nmigen import Signal, Cat, Const, Mux, Module, Array, Elaboratable
 from nmigen.cli import verilog, rtlil
 from nmigen.lib.coding import PriorityEncoder
 from nmigen.hdl.rec import Record, Layout
 from nmigen.cli import verilog, rtlil
 from nmigen.lib.coding import PriorityEncoder
 from nmigen.hdl.rec import Record, Layout
-from stageapi import _spec
+from nmutil.stageapi import _spec
 
 from collections.abc import Sequence
 
 
 from collections.abc import Sequence
 
index bd5e5544f319f8421be593ac0097a71b01536719..bdf14572f843078a72765e0ae469783fdf504e2f 100644 (file)
@@ -18,7 +18,7 @@ from nmigen.hdl.rec import Record, Layout
 from abc import ABCMeta, abstractmethod
 from collections.abc import Sequence, Iterable
 from collections import OrderedDict
 from abc import ABCMeta, abstractmethod
 from collections.abc import Sequence, Iterable
 from collections import OrderedDict
-from queue import Queue
+from nmutil.queue import Queue
 import inspect
 
 
 import inspect
 
 
index afcee743982175e31a833a208067bf59ec2c3978..812b5278df8e55aa02c4bf2d085df943eea49788 100644 (file)
@@ -8,9 +8,9 @@ from nmigen import tracer
 from nmigen.compat.fhdl.bitcontainer import value_bits_sign
 from contextlib import contextmanager
 
 from nmigen.compat.fhdl.bitcontainer import value_bits_sign
 from contextlib import contextmanager
 
-from nmoperator import eq
-from singlepipe import StageCls, ControlBase, BufferedHandshake
-from singlepipe import UnbufferedPipeline
+from nmutil.nmoperator import eq
+from nmutil.singlepipe import StageCls, ControlBase, BufferedHandshake
+from nmutil.singlepipe import UnbufferedPipeline
 
 
 # The following example shows how pyrtl can be used to make some interesting
 
 
 # The following example shows how pyrtl can be used to make some interesting
diff --git a/src/nmutil/queue.py b/src/nmutil/queue.py
new file mode 100644 (file)
index 0000000..0038953
--- /dev/null
@@ -0,0 +1,190 @@
+# Copyright (c) 2014 - 2019 The Regents of the University of
+# California (Regents). All Rights Reserved.  Redistribution and use in
+# source and binary forms, with or without modification, are permitted
+# provided that the following conditions are met:
+#    * Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      two paragraphs of disclaimer.
+#    * Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      two paragraphs of disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#    * Neither the name of the Regents nor the names of its contributors
+#      may be used to endorse or promote products derived from this
+#      software without specific prior written permission.
+# IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
+# ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
+# REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF
+# ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION
+# TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
+# MODIFICATIONS.
+
+from nmigen import Module, Signal, Memory, Mux, Elaboratable
+from nmigen.tools import bits_for
+from nmigen.cli import main
+from nmigen.lib.fifo import FIFOInterface
+
+# translated from https://github.com/freechipsproject/chisel3/blob/a4a29e29c3f1eed18f851dcf10bdc845571dfcb6/src/main/scala/chisel3/util/Decoupled.scala#L185   # noqa
+
+
+class Queue(FIFOInterface, Elaboratable):
+    def __init__(self, width, depth, fwft=True, pipe=False):
+        """ Queue (FIFO) with pipe mode and first-write fall-through capability
+
+            * :width: width of Queue data in/out
+            * :depth: queue depth.  NOTE: may be set to 0 (this is ok)
+            * :fwft : first-write, fall-through mode (Chisel Queue "flow" mode)
+            * :pipe : pipe mode.  NOTE: this mode can cause unanticipated
+                      problems.  when read is enabled, so is writeable.
+                      therefore if read is enabled, the data ABSOLUTELY MUST
+                      be read.
+
+            fwft mode = True basically means that the data may be transferred
+            combinatorially from input to output.
+
+            Attributes:
+            * level: available free space (number of unread entries)
+
+            din  = enq_data, writable  = enq_ready, we = enq_valid
+            dout = deq_data, re = deq_ready, readable = deq_valid
+        """
+        FIFOInterface.__init__(self, width, depth, fwft)
+        self.pipe = pipe
+        self.depth = depth
+        self.level = Signal(bits_for(depth))
+
+    def elaborate(self, platform):
+        m = Module()
+
+        # set up an SRAM.  XXX bug in Memory: cannot create SRAM of depth 1
+        ram = Memory(self.width, self.depth if self.depth > 1 else 2)
+        m.submodules.ram_read = ram_read = ram.read_port(synchronous=False)
+        m.submodules.ram_write = ram_write = ram.write_port()
+
+        # convenience names
+        p_ready_o = self.writable
+        p_valid_i = self.we
+        enq_data = self.din
+
+        n_valid_o = self.readable
+        n_ready_i = self.re
+        deq_data = self.dout
+
+        # intermediaries
+        ptr_width = bits_for(self.depth - 1) if self.depth > 1 else 0
+        enq_ptr = Signal(ptr_width) # cyclic pointer to "insert" point (wrport)
+        deq_ptr = Signal(ptr_width) # cyclic pointer to "remove" point (rdport)
+        maybe_full = Signal() # not reset_less (set by sync)
+
+        # temporaries
+        do_enq = Signal(reset_less=True)
+        do_deq = Signal(reset_less=True)
+        ptr_diff = Signal(ptr_width)
+        ptr_match = Signal(reset_less=True)
+        empty = Signal(reset_less=True)
+        full = Signal(reset_less=True)
+        enq_max = Signal(reset_less=True)
+        deq_max = Signal(reset_less=True)
+
+        m.d.comb += [ptr_match.eq(enq_ptr == deq_ptr), # read-ptr = write-ptr
+                     ptr_diff.eq(enq_ptr - deq_ptr),
+                     enq_max.eq(enq_ptr == self.depth - 1),
+                     deq_max.eq(deq_ptr == self.depth - 1),
+                     empty.eq(ptr_match & ~maybe_full),
+                     full.eq(ptr_match & maybe_full),
+                     do_enq.eq(p_ready_o & p_valid_i), # write conditions ok
+                     do_deq.eq(n_ready_i & n_valid_o), # read conditions ok
+
+                     # set readable and writable (NOTE: see pipe mode below)
+                     n_valid_o.eq(~empty), # cannot read if empty!
+                     p_ready_o.eq(~full),  # cannot write if full!
+
+                     # set up memory and connect to input and output
+                     ram_write.addr.eq(enq_ptr),
+                     ram_write.data.eq(enq_data),
+                     ram_write.en.eq(do_enq),
+                     ram_read.addr.eq(deq_ptr),
+                     deq_data.eq(ram_read.data) # NOTE: overridden in fwft mode
+                    ]
+
+        # under write conditions, SRAM write-pointer moves on next clock
+        with m.If(do_enq):
+            m.d.sync += enq_ptr.eq(Mux(enq_max, 0, enq_ptr+1))
+
+        # under read conditions, SRAM read-pointer moves on next clock
+        with m.If(do_deq):
+            m.d.sync += deq_ptr.eq(Mux(deq_max, 0, deq_ptr+1))
+
+        # if read-but-not-write or write-but-not-read, maybe_full set
+        with m.If(do_enq != do_deq):
+            m.d.sync += maybe_full.eq(do_enq)
+
+        # first-word fall-through: same as "flow" parameter in Chisel3 Queue
+        # basically instead of relying on the Memory characteristics (which
+        # in FPGAs do not have write-through), then when the queue is empty
+        # take the output directly from the input, i.e. *bypass* the SRAM.
+        # this done combinatorially to give the exact same characteristics
+        # as Memory "write-through"... without relying on a changing API
+        if self.fwft:
+            with m.If(p_valid_i):
+                m.d.comb += n_valid_o.eq(1)
+            with m.If(empty):
+                m.d.comb += deq_data.eq(enq_data)
+                m.d.comb += do_deq.eq(0)
+                with m.If(n_ready_i):
+                    m.d.comb += do_enq.eq(0)
+
+        # pipe mode: if next stage says it's ready (readable), we
+        #            *must* declare the input ready (writeable).
+        if self.pipe:
+            with m.If(n_ready_i):
+                m.d.comb += p_ready_o.eq(1)
+
+        # set the count (available free space), optimise on power-of-two
+        if self.depth == 1 << ptr_width:  # is depth a power of 2
+            m.d.comb += self.level.eq(
+                Mux(maybe_full & ptr_match, self.depth, 0) | ptr_diff)
+        else:
+            m.d.comb += self.level.eq(Mux(ptr_match,
+                                          Mux(maybe_full, self.depth, 0),
+                                          Mux(deq_ptr > enq_ptr,
+                                              self.depth + ptr_diff,
+                                              ptr_diff)))
+
+        return m
+
+
+if __name__ == "__main__":
+    reg_stage = Queue(1, 1, pipe=True)
+    break_ready_chain_stage = Queue(1, 1, pipe=True, fwft=True)
+    m = Module()
+    ports = []
+
+    def queue_ports(queue, name_prefix):
+        retval = []
+        for name in ["level",
+                     "dout",
+                     "readable",
+                     "writable"]:
+            port = getattr(queue, name)
+            signal = Signal(port.shape(), name=name_prefix+name)
+            m.d.comb += signal.eq(port)
+            retval.append(signal)
+        for name in ["re",
+                     "din",
+                     "we"]:
+            port = getattr(queue, name)
+            signal = Signal(port.shape(), name=name_prefix+name)
+            m.d.comb += port.eq(signal)
+            retval.append(signal)
+        return retval
+
+    m.submodules.reg_stage = reg_stage
+    ports += queue_ports(reg_stage, "reg_stage_")
+    m.submodules.break_ready_chain_stage = break_ready_chain_stage
+    ports += queue_ports(break_ready_chain_stage, "break_ready_chain_stage_")
+    main(m, ports=ports)
index 68b62e432d4fc6022b99361c3358d01638414fce..b9214bd590446e1e56ed4c5947a4b24abc2a03f8 100644 (file)
@@ -132,12 +132,12 @@ from nmigen import Signal, Mux, Module, Elaboratable
 from nmigen.cli import verilog, rtlil
 from nmigen.hdl.rec import Record
 
 from nmigen.cli import verilog, rtlil
 from nmigen.hdl.rec import Record
 
-from queue import Queue
+from nmutil.queue import Queue
 import inspect
 
 import inspect
 
-from iocontrol import (PrevControl, NextControl, Object, RecordObject)
-from stageapi import (_spec, StageCls, Stage, StageChain, StageHelper)
-import nmoperator
+from nmutil.iocontrol import (PrevControl, NextControl, Object, RecordObject)
+from nmutil.stageapi import (_spec, StageCls, Stage, StageChain, StageHelper)
+from nmutil import nmoperator
                       
 
 class RecordBasedStage(Stage):
                       
 
 class RecordBasedStage(Stage):
diff --git a/src/nmutil/stageapi.py b/src/nmutil/stageapi.py
new file mode 100644 (file)
index 0000000..33fd995
--- /dev/null
@@ -0,0 +1,271 @@
+""" Stage API
+
+    Associated development bugs:
+    * http://bugs.libre-riscv.org/show_bug.cgi?id=64
+    * http://bugs.libre-riscv.org/show_bug.cgi?id=57
+
+    Stage API:
+    ---------
+
+    stage requires compliance with a strict API that may be
+    implemented in several means, including as a static class.
+
+    Stages do not HOLD data, and they definitely do not contain
+    signalling (ready/valid).  They do however specify the FORMAT
+    of the incoming and outgoing data, and they provide a means to
+    PROCESS that data (from incoming format to outgoing format).
+
+    Stage Blocks really should be combinatorial blocks (Moore FSMs).
+    It would be ok to have input come in from sync'd sources
+    (clock-driven, Mealy FSMs) 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.
+    https://en.wikipedia.org/wiki/Moore_machine
+    https://en.wikipedia.org/wiki/Mealy_machine
+
+    the methods of a stage instance must be as follows:
+
+    * 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 worked examples.
+
+    * ospec() - Output data format specification.
+                format requirements identical to ispec.
+
+    * process(m, i) - Optional function for processing ispec-formatted data.
+                returns a combinatorial block of a result that
+                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
+                for adding those submodules to the module (m).
+                the submodules must be combinatorial blocks and
+                must have their inputs and output linked combinatorially.
+
+    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.  See Liskov Substitution Principle:
+    https://en.wikipedia.org/wiki/Liskov_substitution_principle
+
+    StageChain:
+    ----------
+
+    A useful combinatorial wrapper around stages that chains them together
+    and then presents a Stage-API-conformant interface.  By presenting
+    the same API as the stages it wraps, it can clearly be used recursively.
+
+    StageHelper:
+    ----------
+
+    A convenience wrapper around a Stage-API-compliant "thing" which
+    complies with the Stage API and provides mandatory versions of
+    all the optional bits.
+"""
+
+from abc import ABCMeta, abstractmethod
+import inspect
+
+from nmutil import nmoperator
+
+
+def _spec(fn, name=None):
+    """ useful function that determines if "fn" has an argument "name".
+        if so, fn(name) is called otherwise fn() is called.
+
+        means that ispec and ospec can be declared with *or without*
+        a name argument.  normally it would be necessary to have
+        "ispec(name=None)" to achieve the same effect.
+    """
+    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 StageCls(metaclass=ABCMeta):
+    """ Class-based "Stage" API.  requires instantiation (after derivation)
+
+        see "Stage API" above..  Note: python does *not* require derivation
+        from this class.  All that is required is that the pipelines *have*
+        the functions listed in this class.  Derivation from this class
+        is therefore merely a "courtesy" to maintainers.
+    """
+    @abstractmethod
+    def ispec(self): pass       # REQUIRED
+    @abstractmethod
+    def ospec(self): pass       # REQUIRED
+    #@abstractmethod
+    #def setup(self, m, i): pass # OPTIONAL
+    #@abstractmethod
+    #def process(self, i): pass  # OPTIONAL
+
+
+class Stage(metaclass=ABCMeta):
+    """ Static "Stage" API.  does not require instantiation (after derivation)
+
+        see "Stage API" above.  Note: python does *not* require derivation
+        from this class.  All that is required is that the pipelines *have*
+        the functions listed in this class.  Derivation from this class
+        is therefore merely a "courtesy" to maintainers.
+    """
+    @staticmethod
+    @abstractmethod
+    def ispec(): pass
+
+    @staticmethod
+    @abstractmethod
+    def ospec(): pass
+
+    #@staticmethod
+    #@abstractmethod
+    #def setup(m, i): pass
+
+    #@staticmethod
+    #@abstractmethod
+    #def process(i): pass
+
+
+class StageHelper(Stage):
+    """ a convenience wrapper around something that is Stage-API-compliant.
+        (that "something" may be a static class, for example).
+
+        StageHelper happens to also be compliant with the Stage API,
+        it differs from the stage that it wraps in that all the "optional"
+        functions are provided (hence the designation "convenience wrapper")
+    """
+    def __init__(self, stage):
+        self.stage = stage
+        self._ispecfn = None
+        self._ospecfn = None
+        if stage is not None:
+            self.set_specs(self, self)
+
+    def ospec(self, name):
+        assert self._ospecfn is not None
+        return _spec(self._ospecfn, name)
+
+    def ispec(self, name):
+        assert self._ispecfn is not None
+        return _spec(self._ispecfn, name)
+
+    def set_specs(self, p, n):
+        """ sets up the ispecfn and ospecfn for getting input and output data
+        """
+        if hasattr(p, "stage"):
+            p = p.stage
+        if hasattr(n, "stage"):
+            n = n.stage
+        self._ispecfn = p.ispec
+        self._ospecfn = n.ospec
+
+    def new_specs(self, name):
+        """ allocates new ispec and ospec pair
+        """
+        return (_spec(self.ispec, "%s_i" % name),
+                _spec(self.ospec, "%s_o" % name))
+
+    def process(self, i):
+        if self.stage and hasattr(self.stage, "process"):
+            return self.stage.process(i)
+        return i
+
+    def setup(self, m, i):
+        if self.stage is not None and hasattr(self.stage, "setup"):
+            self.stage.setup(m, i)
+
+    def _postprocess(self, i): # XXX DISABLED
+        return i # RETURNS INPUT
+        if hasattr(self.stage, "postprocess"):
+            return self.stage.postprocess(i)
+        return i
+
+
+class StageChain(StageHelper):
+    """ pass in a list of stages, and they will automatically be
+        chained together via their input and output specs into a
+        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.)
+        * 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: 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
+                         "driving from two sources, module is being flattened"
+                         will be issued.
+
+        NOTE: do NOT use StageChain with combinatorial blocks that have
+        side-effects (state-based / clock-based input) or conditional
+        (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
+        StageHelper.__init__(self, None)
+        self.setup = self._sa_setup if specallocate else self._na_setup
+        self.set_specs(self.chain[0], self.chain[-1])
+
+    def _sa_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
+            ofn = self.chain[idx].ospec     # last assignment survives
+            o = _spec(ofn, 'chainin%d' % idx)
+            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 += nmoperator.eq(i, o) # assign to next input
+        self.o = o
+        return self.o                       # last loop is the output
+
+    def _na_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
+            i = o = c.process(i)            # store input into "o"
+        self.o = o
+        return self.o                       # last loop is the output
+
+    def process(self, i):
+        return self.o # conform to Stage API: return last-loop output
+
+
diff --git a/src/nmutil/test/example_buf_pipe.py b/src/nmutil/test/example_buf_pipe.py
new file mode 100644 (file)
index 0000000..61e9b13
--- /dev/null
@@ -0,0 +1,103 @@
+""" Pipeline and BufferedHandshake examples
+"""
+
+from nmutil.nmoperator import eq
+from nmutil.iocontrol import (PrevControl, NextControl)
+from nmutil.singlepipe import (PrevControl, NextControl, ControlBase,
+                        StageCls, Stage, StageChain,
+                        BufferedHandshake, UnbufferedPipeline)
+
+from nmigen import Signal, Module
+from nmigen.cli import verilog, rtlil
+
+
+class ExampleAddStage(StageCls):
+    """ an example of how to use the buffered pipeline, as a class instance
+    """
+
+    def ispec(self):
+        """ returns a tuple of input signals which will be the incoming data
+        """
+        return (Signal(16), Signal(16))
+
+    def ospec(self):
+        """ returns an output signal which will happen to contain the sum
+            of the two inputs
+        """
+        return Signal(16)
+
+    def process(self, i):
+        """ process the input data (sums the values in the tuple) and returns it
+        """
+        return i[0] + i[1]
+
+
+class ExampleBufPipeAdd(BufferedHandshake):
+    """ an example of how to use the buffered pipeline, using a class instance
+    """
+
+    def __init__(self):
+        addstage = ExampleAddStage()
+        BufferedHandshake.__init__(self, addstage)
+
+
+class ExampleStage(Stage):
+    """ an example of how to use the buffered pipeline, in a static class
+        fashion
+    """
+
+    def ispec():
+        return Signal(16, name="example_input_signal")
+
+    def ospec():
+        return Signal(16, name="example_output_signal")
+
+    def process(i):
+        """ process the input data and returns it (adds 1)
+        """
+        return i + 1
+
+
+class ExampleStageCls(StageCls):
+    """ an example of how to use the buffered pipeline, in a static class
+        fashion
+    """
+
+    def ispec(self):
+        return Signal(16, name="example_input_signal")
+
+    def ospec(self):
+        return Signal(16, name="example_output_signal")
+
+    def process(self, i):
+        """ process the input data and returns it (adds 1)
+        """
+        return i + 1
+
+
+class ExampleBufPipe(BufferedHandshake):
+    """ an example of how to use the buffered pipeline.
+    """
+
+    def __init__(self):
+        BufferedHandshake.__init__(self, ExampleStage)
+
+
+class ExamplePipeline(UnbufferedPipeline):
+    """ an example of how to use the unbuffered pipeline.
+    """
+
+    def __init__(self):
+        UnbufferedPipeline.__init__(self, ExampleStage)
+
+
+if __name__ == '__main__':
+    dut = ExampleBufPipe()
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_bufpipe.il", "w") as f:
+        f.write(vl)
+
+    dut = ExamplePipeline()
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_combpipe.il", "w") as f:
+        f.write(vl)
diff --git a/src/nmutil/test/test_buf_pipe.py b/src/nmutil/test/test_buf_pipe.py
new file mode 100644 (file)
index 0000000..089163c
--- /dev/null
@@ -0,0 +1,1308 @@
+""" Unit tests for Buffered and Unbuffered pipelines
+
+    contains useful worked examples of how to use the Pipeline API,
+    including:
+
+    * Combinatorial Stage "Chaining"
+    * class-based data stages
+    * nmigen module-based data stages
+    * special nmigen module-based data stage, where the stage *is* the module
+    * Record-based data stages
+    * static-class data stages
+    * multi-stage pipelines (and how to connect them)
+    * how to *use* the pipelines (see Test5) - how to get data in and out
+
+"""
+
+from nmigen import Module, Signal, Mux, Const, Elaboratable
+from nmigen.hdl.rec import Record
+from nmigen.compat.sim import run_simulation
+from nmigen.cli import verilog, rtlil
+
+from example_buf_pipe import ExampleBufPipe, ExampleBufPipeAdd
+from example_buf_pipe import ExamplePipeline, UnbufferedPipeline
+from example_buf_pipe import ExampleStageCls
+from example_buf_pipe import PrevControl, NextControl, BufferedHandshake
+from example_buf_pipe import StageChain, ControlBase, StageCls
+from nmutil.singlepipe import UnbufferedPipeline2
+from nmutil.singlepipe import SimpleHandshake
+from nmutil.singlepipe import PassThroughHandshake
+from nmutil.singlepipe import PassThroughStage
+from nmutil.singlepipe import FIFOControl
+from nmutil.singlepipe import RecordObject
+
+from random import randint, seed
+
+#seed(4)
+
+
+def check_o_n_valid(dut, val):
+    o_n_valid = yield dut.n.valid_o
+    assert o_n_valid == val
+
+def check_o_n_valid2(dut, val):
+    o_n_valid = yield dut.n.valid_o
+    assert o_n_valid == val
+
+
+def tbench(dut):
+    #yield dut.i_p_rst.eq(1)
+    yield dut.n.ready_i.eq(0)
+    #yield dut.p.ready_o.eq(0)
+    yield
+    yield
+    #yield dut.i_p_rst.eq(0)
+    yield dut.n.ready_i.eq(1)
+    yield dut.p.data_i.eq(5)
+    yield dut.p.valid_i.eq(1)
+    yield
+
+    yield dut.p.data_i.eq(7)
+    yield from check_o_n_valid(dut, 0) # effects of i_p_valid delayed
+    yield
+    yield from check_o_n_valid(dut, 1) # ok *now* i_p_valid effect is felt
+
+    yield dut.p.data_i.eq(2)
+    yield
+    yield dut.n.ready_i.eq(0) # begin going into "stall" (next stage says ready)
+    yield dut.p.data_i.eq(9)
+    yield
+    yield dut.p.valid_i.eq(0)
+    yield dut.p.data_i.eq(12)
+    yield
+    yield dut.p.data_i.eq(32)
+    yield dut.n.ready_i.eq(1)
+    yield
+    yield from check_o_n_valid(dut, 1) # buffer still needs to output
+    yield
+    yield from check_o_n_valid(dut, 1) # buffer still needs to output
+    yield
+    yield from check_o_n_valid(dut, 0) # buffer outputted, *now* we're done.
+    yield
+
+
+def tbench2(dut):
+    #yield dut.p.i_rst.eq(1)
+    yield dut.n.ready_i.eq(0)
+    #yield dut.p.ready_o.eq(0)
+    yield
+    yield
+    #yield dut.p.i_rst.eq(0)
+    yield dut.n.ready_i.eq(1)
+    yield dut.p.data_i.eq(5)
+    yield dut.p.valid_i.eq(1)
+    yield
+
+    yield dut.p.data_i.eq(7)
+    yield from check_o_n_valid2(dut, 0) # effects of i_p_valid delayed 2 clocks
+    yield
+    yield from check_o_n_valid2(dut, 0) # effects of i_p_valid delayed 2 clocks
+
+    yield dut.p.data_i.eq(2)
+    yield
+    yield from check_o_n_valid2(dut, 1) # ok *now* i_p_valid effect is felt
+    yield dut.n.ready_i.eq(0) # begin going into "stall" (next stage says ready)
+    yield dut.p.data_i.eq(9)
+    yield
+    yield dut.p.valid_i.eq(0)
+    yield dut.p.data_i.eq(12)
+    yield
+    yield dut.p.data_i.eq(32)
+    yield dut.n.ready_i.eq(1)
+    yield
+    yield from check_o_n_valid2(dut, 1) # buffer still needs to output
+    yield
+    yield from check_o_n_valid2(dut, 1) # buffer still needs to output
+    yield
+    yield from check_o_n_valid2(dut, 1) # buffer still needs to output
+    yield
+    yield from check_o_n_valid2(dut, 0) # buffer outputted, *now* we're done.
+    yield
+    yield
+    yield
+
+
+class Test3:
+    def __init__(self, dut, resultfn):
+        self.dut = dut
+        self.resultfn = resultfn
+        self.data = []
+        for i in range(num_tests):
+            #data.append(randint(0, 1<<16-1))
+            self.data.append(i+1)
+        self.i = 0
+        self.o = 0
+
+    def send(self):
+        while self.o != len(self.data):
+            send_range = randint(0, 3)
+            for j in range(randint(1,10)):
+                if send_range == 0:
+                    send = True
+                else:
+                    send = randint(0, send_range) != 0
+                o_p_ready = yield self.dut.p.ready_o
+                if not o_p_ready:
+                    yield
+                    continue
+                if send and self.i != len(self.data):
+                    yield self.dut.p.valid_i.eq(1)
+                    yield self.dut.p.data_i.eq(self.data[self.i])
+                    self.i += 1
+                else:
+                    yield self.dut.p.valid_i.eq(0)
+                yield
+
+    def rcv(self):
+        while self.o != len(self.data):
+            stall_range = randint(0, 3)
+            for j in range(randint(1,10)):
+                stall = randint(0, stall_range) != 0
+                yield self.dut.n.ready_i.eq(stall)
+                yield
+                o_n_valid = yield self.dut.n.valid_o
+                i_n_ready = yield self.dut.n.ready_i_test
+                if not o_n_valid or not i_n_ready:
+                    continue
+                data_o = yield self.dut.n.data_o
+                self.resultfn(data_o, self.data[self.o], self.i, self.o)
+                self.o += 1
+                if self.o == len(self.data):
+                    break
+
+def resultfn_3(data_o, expected, i, o):
+    assert data_o == expected + 1, \
+                "%d-%d data %x not match %x\n" \
+                % (i, o, data_o, expected)
+
+def data_placeholder():
+        data = []
+        for i in range(num_tests):
+            d = PlaceHolder()
+            d.src1 = randint(0, 1<<16-1)
+            d.src2 = randint(0, 1<<16-1)
+            data.append(d)
+        return data
+
+def data_dict():
+        data = []
+        for i in range(num_tests):
+            data.append({'src1': randint(0, 1<<16-1),
+                         'src2': randint(0, 1<<16-1)})
+        return data
+
+
+class Test5:
+    def __init__(self, dut, resultfn, data=None, stage_ctl=False):
+        self.dut = dut
+        self.resultfn = resultfn
+        self.stage_ctl = stage_ctl
+        if data:
+            self.data = data
+        else:
+            self.data = []
+            for i in range(num_tests):
+                self.data.append((randint(0, 1<<16-1), randint(0, 1<<16-1)))
+        self.i = 0
+        self.o = 0
+
+    def send(self):
+        while self.o != len(self.data):
+            send_range = randint(0, 3)
+            for j in range(randint(1,10)):
+                if send_range == 0:
+                    send = True
+                else:
+                    send = randint(0, send_range) != 0
+                #send = True
+                o_p_ready = yield self.dut.p.ready_o
+                if not o_p_ready:
+                    yield
+                    continue
+                if send and self.i != len(self.data):
+                    yield self.dut.p.valid_i.eq(1)
+                    for v in self.dut.set_input(self.data[self.i]):
+                        yield v
+                    self.i += 1
+                else:
+                    yield self.dut.p.valid_i.eq(0)
+                yield
+
+    def rcv(self):
+        while self.o != len(self.data):
+            stall_range = randint(0, 3)
+            for j in range(randint(1,10)):
+                ready = randint(0, stall_range) != 0
+                #ready = True
+                yield self.dut.n.ready_i.eq(ready)
+                yield
+                o_n_valid = yield self.dut.n.valid_o
+                i_n_ready = yield self.dut.n.ready_i_test
+                if not o_n_valid or not i_n_ready:
+                    continue
+                if isinstance(self.dut.n.data_o, Record):
+                    data_o = {}
+                    dod = self.dut.n.data_o
+                    for k, v in dod.fields.items():
+                        data_o[k] = yield v
+                else:
+                    data_o = yield self.dut.n.data_o
+                self.resultfn(data_o, self.data[self.o], self.i, self.o)
+                self.o += 1
+                if self.o == len(self.data):
+                    break
+
+def resultfn_5(data_o, expected, i, o):
+    res = expected[0] + expected[1]
+    assert data_o == res, \
+                "%d-%d data %x not match %s\n" \
+                % (i, o, data_o, repr(expected))
+
+def tbench4(dut):
+    data = []
+    for i in range(num_tests):
+        #data.append(randint(0, 1<<16-1))
+        data.append(i+1)
+    i = 0
+    o = 0
+    while True:
+        stall = randint(0, 3) != 0
+        send = randint(0, 5) != 0
+        yield dut.n.ready_i.eq(stall)
+        o_p_ready = yield dut.p.ready_o
+        if o_p_ready:
+            if send and i != len(data):
+                yield dut.p.valid_i.eq(1)
+                yield dut.p.data_i.eq(data[i])
+                i += 1
+            else:
+                yield dut.p.valid_i.eq(0)
+        yield
+        o_n_valid = yield dut.n.valid_o
+        i_n_ready = yield dut.n.ready_i_test
+        if o_n_valid and i_n_ready:
+            data_o = yield dut.n.data_o
+            assert data_o == data[o] + 2, "%d-%d data %x not match %x\n" \
+                                        % (i, o, data_o, data[o])
+            o += 1
+            if o == len(data):
+                break
+
+######################################################################
+# Test 2 and 4
+######################################################################
+
+class ExampleBufPipe2(ControlBase):
+    """ Example of how to do chained pipeline stages.
+    """
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        pipe1 = ExampleBufPipe()
+        pipe2 = ExampleBufPipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+
+######################################################################
+# Test 9
+######################################################################
+
+class ExampleBufPipeChain2(BufferedHandshake):
+    """ connects two stages together as a *single* combinatorial stage.
+    """
+    def __init__(self):
+        stage1 = ExampleStageCls()
+        stage2 = ExampleStageCls()
+        combined = StageChain([stage1, stage2])
+        BufferedHandshake.__init__(self, combined)
+
+
+def data_chain2():
+        data = []
+        for i in range(num_tests):
+            data.append(randint(0, 1<<16-2))
+        return data
+
+
+def resultfn_9(data_o, expected, i, o):
+    res = expected + 2
+    assert data_o == res, \
+                "%d-%d received data %x not match expected %x\n" \
+                % (i, o, data_o, res)
+
+
+######################################################################
+# Test 6 and 10
+######################################################################
+
+class SetLessThan(Elaboratable):
+    def __init__(self, width, signed):
+        self.m = Module()
+        self.src1 = Signal((width, signed), name="src1")
+        self.src2 = Signal((width, signed), name="src2")
+        self.output = Signal(width, name="out")
+
+    def elaborate(self, platform):
+        self.m.d.comb += self.output.eq(Mux(self.src1 < self.src2, 1, 0))
+        return self.m
+
+
+class LTStage(StageCls):
+    """ module-based stage example
+    """
+    def __init__(self):
+        self.slt = SetLessThan(16, True)
+
+    def ispec(self, name):
+        return (Signal(16, name="%s_sig1" % name),
+                Signal(16, name="%s_sig2" % name))
+
+    def ospec(self, name):
+        return Signal(16, "%s_out" % name)
+
+    def setup(self, m, i):
+        self.o = Signal(16)
+        m.submodules.slt = self.slt
+        m.d.comb += self.slt.src1.eq(i[0])
+        m.d.comb += self.slt.src2.eq(i[1])
+        m.d.comb += self.o.eq(self.slt.output)
+
+    def process(self, i):
+        return self.o
+
+
+class LTStageDerived(SetLessThan, StageCls):
+    """ special version of a nmigen module where the module is also a stage
+
+        shows that you don't actually need to combinatorially connect
+        to the outputs, or add the module as a submodule: just return
+        the module output parameter(s) from the Stage.process() function
+    """
+
+    def __init__(self):
+        SetLessThan.__init__(self, 16, True)
+
+    def ispec(self):
+        return (Signal(16), Signal(16))
+
+    def ospec(self):
+        return Signal(16)
+
+    def setup(self, m, i):
+        m.submodules.slt = self
+        m.d.comb += self.src1.eq(i[0])
+        m.d.comb += self.src2.eq(i[1])
+
+    def process(self, i):
+        return self.output
+
+
+class ExampleLTPipeline(UnbufferedPipeline):
+    """ an example of how to use the unbuffered pipeline.
+    """
+
+    def __init__(self):
+        stage = LTStage()
+        UnbufferedPipeline.__init__(self, stage)
+
+
+class ExampleLTBufferedPipeDerived(BufferedHandshake):
+    """ an example of how to use the buffered pipeline.
+    """
+
+    def __init__(self):
+        stage = LTStageDerived()
+        BufferedHandshake.__init__(self, stage)
+
+
+def resultfn_6(data_o, expected, i, o):
+    res = 1 if expected[0] < expected[1] else 0
+    assert data_o == res, \
+                "%d-%d data %x not match %s\n" \
+                % (i, o, data_o, repr(expected))
+
+
+######################################################################
+# Test 7
+######################################################################
+
+class ExampleAddRecordStage(StageCls):
+    """ example use of a Record
+    """
+
+    record_spec = [('src1', 16), ('src2', 16)]
+    def ispec(self):
+        """ returns a Record using the specification
+        """
+        return Record(self.record_spec)
+
+    def ospec(self):
+        return Record(self.record_spec)
+
+    def process(self, i):
+        """ process the input data, returning a dictionary with key names
+            that exactly match the Record's attributes.
+        """
+        return {'src1': i.src1 + 1,
+                'src2': i.src2 + 1}
+
+######################################################################
+# Test 11
+######################################################################
+
+class ExampleAddRecordPlaceHolderStage(StageCls):
+    """ example use of a Record, with a placeholder as the processing result
+    """
+
+    record_spec = [('src1', 16), ('src2', 16)]
+    def ispec(self):
+        """ returns a Record using the specification
+        """
+        return Record(self.record_spec)
+
+    def ospec(self):
+        return Record(self.record_spec)
+
+    def process(self, i):
+        """ process the input data, returning a PlaceHolder class instance
+            with attributes that exactly match those of the Record.
+        """
+        o = PlaceHolder()
+        o.src1 = i.src1 + 1
+        o.src2 = i.src2 + 1
+        return o
+
+
+# a dummy class that may have stuff assigned to instances once created
+class PlaceHolder: pass
+
+
+class ExampleAddRecordPipe(UnbufferedPipeline):
+    """ an example of how to use the combinatorial pipeline.
+    """
+
+    def __init__(self):
+        stage = ExampleAddRecordStage()
+        UnbufferedPipeline.__init__(self, stage)
+
+
+def resultfn_7(data_o, expected, i, o):
+    res = (expected['src1'] + 1, expected['src2'] + 1)
+    assert data_o['src1'] == res[0] and data_o['src2'] == res[1], \
+                "%d-%d data %s not match %s\n" \
+                % (i, o, repr(data_o), repr(expected))
+
+
+class ExampleAddRecordPlaceHolderPipe(UnbufferedPipeline):
+    """ an example of how to use the combinatorial pipeline.
+    """
+
+    def __init__(self):
+        stage = ExampleAddRecordPlaceHolderStage()
+        UnbufferedPipeline.__init__(self, stage)
+
+
+def resultfn_test11(data_o, expected, i, o):
+    res1 = expected.src1 + 1
+    res2 = expected.src2 + 1
+    assert data_o['src1'] == res1 and data_o['src2'] == res2, \
+                "%d-%d data %s not match %s\n" \
+                % (i, o, repr(data_o), repr(expected))
+
+
+######################################################################
+# Test 8
+######################################################################
+
+
+class Example2OpClass:
+    """ an example of a class used to store 2 operands.
+        requires an eq function, to conform with the pipeline stage API
+    """
+
+    def __init__(self):
+        self.op1 = Signal(16)
+        self.op2 = Signal(16)
+
+    def eq(self, i):
+        return [self.op1.eq(i.op1), self.op2.eq(i.op2)]
+
+
+class ExampleAddClassStage(StageCls):
+    """ an example of how to use the buffered pipeline, as a class instance
+    """
+
+    def ispec(self):
+        """ returns an instance of an Example2OpClass.
+        """
+        return Example2OpClass()
+
+    def ospec(self):
+        """ returns an output signal which will happen to contain the sum
+            of the two inputs
+        """
+        return Signal(16, name="add2_out")
+
+    def process(self, i):
+        """ process the input data (sums the values in the tuple) and returns it
+        """
+        return i.op1 + i.op2
+
+
+class ExampleBufPipeAddClass(BufferedHandshake):
+    """ an example of how to use the buffered pipeline, using a class instance
+    """
+
+    def __init__(self):
+        addstage = ExampleAddClassStage()
+        BufferedHandshake.__init__(self, addstage)
+
+
+class TestInputAdd:
+    """ the eq function, called by set_input, needs an incoming object
+        that conforms to the Example2OpClass.eq function requirements
+        easiest way to do that is to create a class that has the exact
+        same member layout (self.op1, self.op2) as Example2OpClass
+    """
+    def __init__(self, op1, op2):
+        self.op1 = op1
+        self.op2 = op2
+
+
+def resultfn_8(data_o, expected, i, o):
+    res = expected.op1 + expected.op2 # these are a TestInputAdd instance
+    assert data_o == res, \
+                "%d-%d data %s res %x not match %s\n" \
+                % (i, o, repr(data_o), res, repr(expected))
+
+def data_2op():
+        data = []
+        for i in range(num_tests):
+            data.append(TestInputAdd(randint(0, 1<<16-1), randint(0, 1<<16-1)))
+        return data
+
+
+######################################################################
+# Test 12
+######################################################################
+
+class ExampleStageDelayCls(StageCls, Elaboratable):
+    """ an example of how to use the buffered pipeline, in a static class
+        fashion
+    """
+
+    def __init__(self, valid_trigger=2):
+        self.count = Signal(2)
+        self.valid_trigger = valid_trigger
+
+    def ispec(self):
+        return Signal(16, name="example_input_signal")
+
+    def ospec(self):
+        return Signal(16, name="example_output_signal")
+
+    @property
+    def d_ready(self):
+        """ data is ready to be accepted when this is true
+        """
+        return (self.count == 1)# | (self.count == 3)
+        return Const(1)
+
+    def d_valid(self, ready_i):
+        """ data is valid at output when this is true
+        """
+        return self.count == self.valid_trigger
+        return Const(1)
+
+    def process(self, i):
+        """ process the input data and returns it (adds 1)
+        """
+        return i + 1
+
+    def elaborate(self, platform):
+        m = Module()
+        m.d.sync += self.count.eq(self.count + 1)
+        return m
+
+
+class ExampleBufDelayedPipe(BufferedHandshake):
+
+    def __init__(self):
+        stage = ExampleStageDelayCls(valid_trigger=2)
+        BufferedHandshake.__init__(self, stage, stage_ctl=True)
+
+    def elaborate(self, platform):
+        m = BufferedHandshake.elaborate(self, platform)
+        m.submodules.stage = self.stage
+        return m
+
+
+def data_chain1():
+        data = []
+        for i in range(num_tests):
+            data.append(1<<((i*3)%15))
+            #data.append(randint(0, 1<<16-2))
+            #print (hex(data[-1]))
+        return data
+
+
+def resultfn_12(data_o, expected, i, o):
+    res = expected + 1
+    assert data_o == res, \
+                "%d-%d data %x not match %x\n" \
+                % (i, o, data_o, res)
+
+
+######################################################################
+# Test 13
+######################################################################
+
+class ExampleUnBufDelayedPipe(BufferedHandshake):
+
+    def __init__(self):
+        stage = ExampleStageDelayCls(valid_trigger=3)
+        BufferedHandshake.__init__(self, stage, stage_ctl=True)
+
+    def elaborate(self, platform):
+        m = BufferedHandshake.elaborate(self, platform)
+        m.submodules.stage = self.stage
+        return m
+
+######################################################################
+# Test 15
+######################################################################
+
+class ExampleBufModeAdd1Pipe(SimpleHandshake):
+
+    def __init__(self):
+        stage = ExampleStageCls()
+        SimpleHandshake.__init__(self, stage)
+
+
+######################################################################
+# Test 16
+######################################################################
+
+class ExampleBufModeUnBufPipe(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        pipe1 = ExampleBufModeAdd1Pipe()
+        pipe2 = ExampleBufAdd1Pipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+######################################################################
+# Test 17
+######################################################################
+
+class ExampleUnBufAdd1Pipe2(UnbufferedPipeline2):
+
+    def __init__(self):
+        stage = ExampleStageCls()
+        UnbufferedPipeline2.__init__(self, stage)
+
+
+######################################################################
+# Test 18
+######################################################################
+
+class PassThroughTest(PassThroughHandshake):
+
+    def iospecfn(self):
+        return Signal(16, "out")
+
+    def __init__(self):
+        stage = PassThroughStage(self.iospecfn)
+        PassThroughHandshake.__init__(self, stage)
+
+def resultfn_identical(data_o, expected, i, o):
+    res = expected
+    assert data_o == res, \
+                "%d-%d data %x not match %x\n" \
+                % (i, o, data_o, res)
+
+
+######################################################################
+# Test 19
+######################################################################
+
+class ExamplePassAdd1Pipe(PassThroughHandshake):
+
+    def __init__(self):
+        stage = ExampleStageCls()
+        PassThroughHandshake.__init__(self, stage)
+
+
+class ExampleBufPassThruPipe(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        # XXX currently fails: any other permutation works fine.
+        # p1=u,p2=b ok p1=u,p2=u ok p1=b,p2=b ok
+        # also fails using UnbufferedPipeline as well
+        pipe1 = ExampleBufModeAdd1Pipe()
+        pipe2 = ExamplePassAdd1Pipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+
+######################################################################
+# Test 20
+######################################################################
+
+def iospecfn():
+    return Signal(16, name="d_in")
+
+class FIFOTest16(FIFOControl):
+
+    def __init__(self):
+        stage = PassThroughStage(iospecfn)
+        FIFOControl.__init__(self, 2, stage)
+
+
+######################################################################
+# Test 21
+######################################################################
+
+class ExampleFIFOPassThruPipe1(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        pipe1 = FIFOTest16()
+        pipe2 = FIFOTest16()
+        pipe3 = ExamplePassAdd1Pipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+        m.submodules.pipe3 = pipe3
+
+        m.d.comb += self.connect([pipe1, pipe2, pipe3])
+
+        return m
+
+
+######################################################################
+# Test 22
+######################################################################
+
+class Example2OpRecord(RecordObject):
+    def __init__(self):
+        RecordObject.__init__(self)
+        self.op1 = Signal(16)
+        self.op2 = Signal(16)
+
+
+class ExampleAddRecordObjectStage(StageCls):
+
+    def ispec(self):
+        """ returns an instance of an Example2OpRecord.
+        """
+        return Example2OpRecord()
+
+    def ospec(self):
+        """ returns an output signal which will happen to contain the sum
+            of the two inputs
+        """
+        return Signal(16)
+
+    def process(self, i):
+        """ process the input data (sums the values in the tuple) and returns it
+        """
+        return i.op1 + i.op2
+
+
+class ExampleRecordHandshakeAddClass(SimpleHandshake):
+
+    def __init__(self):
+        addstage = ExampleAddRecordObjectStage()
+        SimpleHandshake.__init__(self, stage=addstage)
+
+
+######################################################################
+# Test 23
+######################################################################
+
+def iospecfnrecord():
+    return Example2OpRecord()
+
+class FIFOTestRecordControl(FIFOControl):
+
+    def __init__(self):
+        stage = PassThroughStage(iospecfnrecord)
+        FIFOControl.__init__(self, 2, stage)
+
+
+class ExampleFIFORecordObjectPipe(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        pipe1 = FIFOTestRecordControl()
+        pipe2 = ExampleRecordHandshakeAddClass()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+
+######################################################################
+# Test 24
+######################################################################
+
+class FIFOTestRecordAddStageControl(FIFOControl):
+
+    def __init__(self):
+        stage = ExampleAddRecordObjectStage()
+        FIFOControl.__init__(self, 2, stage)
+
+
+
+######################################################################
+# Test 25
+######################################################################
+
+class FIFOTestAdd16(FIFOControl):
+
+    def __init__(self):
+        stage = ExampleStageCls()
+        FIFOControl.__init__(self, 2, stage)
+
+
+class ExampleFIFOAdd2Pipe(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        pipe1 = FIFOTestAdd16()
+        pipe2 = FIFOTestAdd16()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+
+######################################################################
+# Test 26
+######################################################################
+
+def iospecfn24():
+    return (Signal(16, name="src1"), Signal(16, name="src2"))
+
+class FIFOTest2x16(FIFOControl):
+
+    def __init__(self):
+        stage = PassThroughStage(iospecfn2)
+        FIFOControl.__init__(self, 2, stage)
+
+
+######################################################################
+# Test 997
+######################################################################
+
+class ExampleBufPassThruPipe2(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        # XXX currently fails: any other permutation works fine.
+        # p1=u,p2=b ok p1=u,p2=u ok p1=b,p2=b ok
+        # also fails using UnbufferedPipeline as well
+        #pipe1 = ExampleUnBufAdd1Pipe()
+        #pipe2 = ExampleBufAdd1Pipe()
+        pipe1 = ExampleBufAdd1Pipe()
+        pipe2 = ExamplePassAdd1Pipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+
+######################################################################
+# Test 998
+######################################################################
+
+class ExampleBufPipe3(ControlBase):
+    """ Example of how to do delayed pipeline, where the stage signals
+        whether it is ready.
+    """
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        pipe1 = ExampleBufDelayedPipe()
+        pipe2 = ExampleBufPipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+######################################################################
+# Test 999 - XXX FAILS
+# http://bugs.libre-riscv.org/show_bug.cgi?id=57
+######################################################################
+
+class ExampleBufAdd1Pipe(BufferedHandshake):
+
+    def __init__(self):
+        stage = ExampleStageCls()
+        BufferedHandshake.__init__(self, stage)
+
+
+class ExampleUnBufAdd1Pipe(UnbufferedPipeline):
+
+    def __init__(self):
+        stage = ExampleStageCls()
+        UnbufferedPipeline.__init__(self, stage)
+
+
+class ExampleBufUnBufPipe(ControlBase):
+
+    def elaborate(self, platform):
+        m = ControlBase.elaborate(self, platform)
+
+        # XXX currently fails: any other permutation works fine.
+        # p1=u,p2=b ok p1=u,p2=u ok p1=b,p2=b ok
+        # also fails using UnbufferedPipeline as well
+        #pipe1 = ExampleUnBufAdd1Pipe()
+        #pipe2 = ExampleBufAdd1Pipe()
+        pipe1 = ExampleBufAdd1Pipe()
+        pipe2 = ExampleUnBufAdd1Pipe()
+
+        m.submodules.pipe1 = pipe1
+        m.submodules.pipe2 = pipe2
+
+        m.d.comb += self.connect([pipe1, pipe2])
+
+        return m
+
+
+######################################################################
+# Unit Tests
+######################################################################
+
+num_tests = 10
+
+if __name__ == '__main__':
+    if False:
+        print ("test 1")
+        dut = ExampleBufPipe()
+        run_simulation(dut, tbench(dut), vcd_name="test_bufpipe.vcd")
+
+        print ("test 2")
+        dut = ExampleBufPipe2()
+        run_simulation(dut, tbench2(dut), vcd_name="test_bufpipe2.vcd")
+        ports = [dut.p.valid_i, dut.n.ready_i,
+                 dut.n.valid_o, dut.p.ready_o] + \
+                 [dut.p.data_i] + [dut.n.data_o]
+        vl = rtlil.convert(dut, ports=ports)
+        with open("test_bufpipe2.il", "w") as f:
+            f.write(vl)
+
+
+    print ("test 3")
+    dut = ExampleBufPipe()
+    test = Test3(dut, resultfn_3)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe3.vcd")
+
+    print ("test 3.5")
+    dut = ExamplePipeline()
+    test = Test3(dut, resultfn_3)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_combpipe3.vcd")
+
+    print ("test 4")
+    dut = ExampleBufPipe2()
+    run_simulation(dut, tbench4(dut), vcd_name="test_bufpipe4.vcd")
+
+    print ("test 5")
+    dut = ExampleBufPipeAdd()
+    test = Test5(dut, resultfn_5, stage_ctl=True)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe5.vcd")
+
+    print ("test 6")
+    dut = ExampleLTPipeline()
+    test = Test5(dut, resultfn_6)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltcomb6.vcd")
+
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             list(dut.p.data_i) + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_ltcomb_pipe.il", "w") as f:
+        f.write(vl)
+
+    print ("test 7")
+    dut = ExampleAddRecordPipe()
+    data=data_dict()
+    test = Test5(dut, resultfn_7, data=data)
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o,
+             dut.p.data_i.src1, dut.p.data_i.src2,
+             dut.n.data_o.src1, dut.n.data_o.src2]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_recordcomb_pipe.il", "w") as f:
+        f.write(vl)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
+
+    print ("test 8")
+    dut = ExampleBufPipeAddClass()
+    data=data_2op()
+    test = Test5(dut, resultfn_8, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe8.vcd")
+
+    print ("test 9")
+    dut = ExampleBufPipeChain2()
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufpipechain2.il", "w") as f:
+        f.write(vl)
+
+    data = data_chain2()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv],
+                        vcd_name="test_bufpipechain2.vcd")
+
+    print ("test 10")
+    dut = ExampleLTBufferedPipeDerived()
+    test = Test5(dut, resultfn_6)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_ltbufpipe10.vcd")
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_ltbufpipe10.il", "w") as f:
+        f.write(vl)
+
+    print ("test 11")
+    dut = ExampleAddRecordPlaceHolderPipe()
+    data=data_placeholder()
+    test = Test5(dut, resultfn_test11, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord.vcd")
+
+
+    print ("test 12")
+    dut = ExampleBufDelayedPipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_12, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe12.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufpipe12.il", "w") as f:
+        f.write(vl)
+
+    print ("test 13")
+    dut = ExampleUnBufDelayedPipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_12, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_unbufpipe13.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_unbufpipe13.il", "w") as f:
+        f.write(vl)
+
+    print ("test 15")
+    dut = ExampleBufModeAdd1Pipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_12, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufunbuf15.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufunbuf15.il", "w") as f:
+        f.write(vl)
+
+    print ("test 16")
+    dut = ExampleBufModeUnBufPipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufunbuf16.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufunbuf16.il", "w") as f:
+        f.write(vl)
+
+    print ("test 17")
+    dut = ExampleUnBufAdd1Pipe2()
+    data = data_chain1()
+    test = Test5(dut, resultfn_12, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_unbufpipe17.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_unbufpipe17.il", "w") as f:
+        f.write(vl)
+
+    print ("test 18")
+    dut = PassThroughTest()
+    data = data_chain1()
+    test = Test5(dut, resultfn_identical, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_passthru18.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_passthru18.il", "w") as f:
+        f.write(vl)
+
+    print ("test 19")
+    dut = ExampleBufPassThruPipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpass19.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufpass19.il", "w") as f:
+        f.write(vl)
+
+    print ("test 20")
+    dut = FIFOTest16()
+    data = data_chain1()
+    test = Test5(dut, resultfn_identical, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_fifo20.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_fifo20.il", "w") as f:
+        f.write(vl)
+
+    print ("test 21")
+    dut = ExampleFIFOPassThruPipe1()
+    data = data_chain1()
+    test = Test5(dut, resultfn_12, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_fifopass21.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_fifopass21.il", "w") as f:
+        f.write(vl)
+
+    print ("test 22")
+    dut = ExampleRecordHandshakeAddClass()
+    data=data_2op()
+    test = Test5(dut, resultfn_8, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord22.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i.op1, dut.p.data_i.op2] + \
+             [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_addrecord22.il", "w") as f:
+        f.write(vl)
+
+    print ("test 23")
+    dut = ExampleFIFORecordObjectPipe()
+    data=data_2op()
+    test = Test5(dut, resultfn_8, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord23.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i.op1, dut.p.data_i.op2] + \
+             [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_addrecord23.il", "w") as f:
+        f.write(vl)
+
+    print ("test 24")
+    dut = FIFOTestRecordAddStageControl()
+    data=data_2op()
+    test = Test5(dut, resultfn_8, data=data)
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i.op1, dut.p.data_i.op2] + \
+             [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_addrecord24.il", "w") as f:
+        f.write(vl)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_addrecord24.vcd")
+
+    print ("test 25")
+    dut = ExampleFIFOAdd2Pipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_add2pipe25.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_add2pipe25.il", "w") as f:
+        f.write(vl)
+
+    print ("test 997")
+    dut = ExampleBufPassThruPipe2()
+    data = data_chain1()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpass997.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufpass997.il", "w") as f:
+        f.write(vl)
+
+    print ("test 998 (fails, bug)")
+    dut = ExampleBufPipe3()
+    data = data_chain1()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufpipe14.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufpipe14.il", "w") as f:
+        f.write(vl)
+
+    print ("test 999 (expected to fail, which is a bug)")
+    dut = ExampleBufUnBufPipe()
+    data = data_chain1()
+    test = Test5(dut, resultfn_9, data=data)
+    run_simulation(dut, [test.send, test.rcv], vcd_name="test_bufunbuf999.vcd")
+    ports = [dut.p.valid_i, dut.n.ready_i,
+             dut.n.valid_o, dut.p.ready_o] + \
+             [dut.p.data_i] + [dut.n.data_o]
+    vl = rtlil.convert(dut, ports=ports)
+    with open("test_bufunbuf999.il", "w") as f:
+        f.write(vl)
+