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 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
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 singlepipe import eq
+from nmutil.singlepipe import eq
 
 
 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 singlepipe import eq
+from nmutil.singlepipe import eq
 
 
 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 singlepipe import PrevControl, NextControl
+from nmutil.singlepipe import PrevControl, NextControl
 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 singlepipe import eq
+from nmutil.singlepipe import eq
 
 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 singlepipe import cat, RecordObject
+from nmutil.singlepipe import cat, RecordObject
 
 
 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 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
 
 
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 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):
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 multipipe import CombMuxOutPipe
-from singlepipe import SimpleHandshake, PassThroughHandshake, RecordObject
+from nmutil.multipipe import CombMuxOutPipe
+from nmutil.singlepipe import SimpleHandshake, PassThroughHandshake, 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 singlepipe import PassThroughStage
-from multipipe import (CombMultiInPipeline, PriorityCombMuxInPipe)
+from nmutil.singlepipe import PassThroughStage
+from nmutil.multipipe import (CombMultiInPipeline, PriorityCombMuxInPipe)
 
 
 class PassData:
index 62452c37c8478dba1dfa030da619a8409322672c..b373f1e3d79347566da70d5d7b149806cc97c50f 100644 (file)
@@ -5,7 +5,7 @@
 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
index 5c4eed18d9aba94a9956c425e216e1ba579192dd..eea893552047d772f8ba7adf930db3a6e729e118 100644 (file)
@@ -5,9 +5,9 @@
 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
index 39254a74f772d4565439d975c0857b4c7d02bd19..978851ef0c99a6aa8829c8c05aadd118cb04bb21 100644 (file)
@@ -7,7 +7,7 @@ from nmigen.cli import main, verilog
 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
index e7f298d3e433eb74c3104625e9d4698066668d17..5afe702e707a6ef74f732c7d94f4c8fe8510a56b 100644 (file)
@@ -8,7 +8,7 @@ from math import log
 
 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)
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 singlepipe import (ControlBase, StageChain, SimpleHandshake,
+from nmutil.singlepipe import (ControlBase, StageChain, SimpleHandshake,
                         PassThroughStage, PrevControl)
-from multipipe import CombMuxOutPipe
-from multipipe import PriorityCombMuxInPipe
+from nmutil.multipipe import CombMuxOutPipe
+from nmutil.multipipe import PriorityCombMuxInPipe
 
 from fpbase import FPState
-import nmoperator
+from nmutil import nmoperator
 
 
 class FPGetOpMod(Elaboratable):
index 7d871f42efe73611a7c47348721e9e1fad07ea8c..ac97bf1cae5225a8b0f1a84f204c69fd92555696 100644 (file)
@@ -4,7 +4,7 @@
 
 #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
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 singlepipe import Object
+from nmutil.singlepipe import 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 stageapi import _spec
+from nmutil.stageapi import _spec
 
 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 queue import Queue
+from nmutil.queue import Queue
 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 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
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 queue import Queue
+from nmutil.queue import Queue
 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):
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)
+