update comments
[ieee754fpu.git] / src / add / singlepipe.py
index ebbb1e9b95401b074f73cb9d3d86ba789c6381c3..68b62e432d4fc6022b99361c3358d01638414fce 100644 (file)
@@ -1,11 +1,10 @@
-""" Pipeline and BufferedHandshake implementation, conforming to the same API.
-    For multi-input and multi-output variants, see multipipe.
+""" Pipeline API.  For multi-input and multi-output variants, see multipipe.
 
     Associated development bugs:
     * http://bugs.libre-riscv.org/show_bug.cgi?id=64
     * http://bugs.libre-riscv.org/show_bug.cgi?id=57
 
-    Important: see Stage API (iocontrol.py) in combination with below
+    Important: see Stage API (stageapi.py) in combination with below
 
     RecordBasedStage:
     ----------------
     https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
 """
 
-from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
+from nmigen import Signal, Mux, Module, Elaboratable
 from nmigen.cli import verilog, rtlil
-from nmigen.lib.fifo import SyncFIFO, SyncFIFOBuffered
-from nmigen.hdl.ast import ArrayProxy
 from nmigen.hdl.rec import Record
 
-from abc import ABCMeta, abstractmethod
-from collections.abc import Sequence, Iterable
-from collections import OrderedDict
 from queue import Queue
 import inspect
 
+from iocontrol import (PrevControl, NextControl, Object, RecordObject)
+from stageapi import (_spec, StageCls, Stage, StageChain, StageHelper)
 import nmoperator
-from iocontrol import (Object, RecordObject)
-from stageapi import (_spec, PrevControl, NextControl, StageCls, Stage,
-                       StageChain, StageHelper)
                       
 
+class RecordBasedStage(Stage):
+    """ convenience class which provides a Records-based layout.
+        honestly it's a lot easier just to create a direct Records-based
+        class (see ExampleAddRecordStage)
+    """
+    def __init__(self, in_shape, out_shape, processfn, setupfn=None):
+        self.in_shape = in_shape
+        self.out_shape = out_shape
+        self.__process = processfn
+        self.__setup = setupfn
+    def ispec(self): return Record(self.in_shape)
+    def ospec(self): return Record(self.out_shape)
+    def process(seif, i): return self.__process(i)
+    def setup(seif, m, i): return self.__setup(m, i)
+
+
+class PassThroughStage(StageCls):
+    """ a pass-through stage with its input data spec identical to its output,
+        and "passes through" its data from input to output (does nothing).
+
+        use this basically to explicitly make any data spec Stage-compliant.
+        (many APIs would potentially use a static "wrap" method in e.g.
+         StageCls to achieve a similar effect)
+    """
+    def __init__(self, iospecfn): self.iospecfn = iospecfn
+    def ispec(self): return self.iospecfn()
+    def ospec(self): return self.iospecfn()
+
 
 class ControlBase(StageHelper, Elaboratable):
     """ Common functions for Pipeline API.  Note: a "pipeline stage" only
@@ -177,13 +198,12 @@ class ControlBase(StageHelper, Elaboratable):
 
         # set up the input and output data
         if stage is not None:
-            self._new_data(self, self, "data")
+            self._new_data("data")
 
-    def _new_data(self, p, n, name):
+    def _new_data(self, name):
         """ allocates new data_i and data_o
         """
-        self.p.data_i = _spec(p.stage.ispec, "%s_i" % name)
-        self.n.data_o = _spec(n.stage.ospec, "%s_o" % name)
+        self.p.data_i, self.n.data_o = self.new_specs(name)
 
     @property
     def data_r(self):
@@ -247,6 +267,7 @@ class ControlBase(StageHelper, Elaboratable):
               an elaborate() to m.d.comb
         """
         assert len(pipechain) > 0, "pipechain must be non-zero length"
+        assert self.stage is None, "do not use connect with a stage"
         eqs = [] # collated list of assignment statements
 
         # connect inter-chain
@@ -258,9 +279,10 @@ class ControlBase(StageHelper, Elaboratable):
         # connect front and back of chain to ourselves
         front = pipechain[0]                # first in chain
         end = pipechain[-1]                 # last in chain
-        self._new_data(front, end, "chain") # NOTE: REPLACES existing data
+        self.set_specs(front, end) # sets up ispec/ospec functions
+        self._new_data("chain") # NOTE: REPLACES existing data
         eqs += front._connect_in(self)      # front p to our p
-        eqs += end._connect_out(self)       # end n   to out n
+        eqs += end._connect_out(self)       # end n   to our n
 
         return eqs
 
@@ -297,21 +319,6 @@ class ControlBase(StageHelper, Elaboratable):
 
         return m
 
-class RecordBasedStage(Stage):
-    """ convenience class which provides a Records-based layout.
-        honestly it's a lot easier just to create a direct Records-based
-        class (see ExampleAddRecordStage)
-    """
-    def __init__(self, in_shape, out_shape, processfn, setupfn=None):
-        self.in_shape = in_shape
-        self.out_shape = out_shape
-        self.__process = processfn
-        self.__setup = setupfn
-    def ispec(self): return Record(self.in_shape)
-    def ospec(self): return Record(self.out_shape)
-    def process(seif, i): return self.__process(i)
-    def setup(seif, m, i): return self.__setup(m, i)
-
 
 class BufferedHandshake(ControlBase):
     """ buffered pipeline stage.  data and strobe signals travel in sync.
@@ -654,19 +661,6 @@ class UnbufferedPipeline2(ControlBase):
         return self.m
 
 
-class PassThroughStage(StageCls):
-    """ a pass-through stage with its input data spec identical to its output,
-        and "passes through" its data from input to output (does nothing).
-
-        use this basically to explicitly make any data spec Stage-compliant.
-        (many APIs would potentially use a static "wrap" method in e.g.
-         StageCls to achieve a similar effect)
-    """
-    def __init__(self, iospecfn): self.iospecfn = iospecfn
-    def ispec(self): return self.iospecfn()
-    def ospec(self): return self.iospecfn()
-
-
 class PassThroughHandshake(ControlBase):
     """ A control block that delays by one clock cycle.
 
@@ -732,23 +726,29 @@ class RegisterPipeline(UnbufferedPipeline):
 
 
 class FIFOControl(ControlBase):
-    """ FIFO Control.  Uses SyncFIFO to store data, coincidentally
+    """ FIFO Control.  Uses Queue to store data, coincidentally
         happens to have same valid/ready signalling as Stage API.
 
         data_i -> fifo.din -> FIFO -> fifo.dout -> data_o
     """
     def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
-                                     fwft=True, buffered=False, pipe=False):
+                                     fwft=True, pipe=False):
         """ FIFO Control
 
-            * :depth:    number of entries in the FIFO
-            * :stage:    data processing block
-            * :fwft:     first word fall-thru mode (non-fwft introduces delay)
-            * :buffered: use buffered FIFO (introduces extra cycle delay)
+            * :depth: number of entries in the FIFO
+            * :stage: data processing block
+            * :fwft:  first word fall-thru mode (non-fwft introduces delay)
+            * :pipe:  specifies pipe mode.
+
+            when fwft = True it indicates that transfers may occur
+            combinatorially through stage processing in the same clock cycle.
+            This requires that the Stage be a Moore FSM:
+            https://en.wikipedia.org/wiki/Moore_machine
 
-            NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
-                    (fwft=True, buffered=False).  XXX TODO: fix this by
-                    using Queue in all cases instead.
+            when fwft = False it indicates that all output signals are
+            produced only from internal registers or memory, i.e. that the
+            Stage is a Mealy FSM:
+            https://en.wikipedia.org/wiki/Mealy_machine
 
             data is processed (and located) as follows:
 
@@ -759,12 +759,7 @@ class FIFOControl(ControlBase):
             this is how the FIFO gets de-catted without needing a de-cat
             function
         """
-
-        assert not (fwft and buffered), "buffered cannot do fwft"
-        if buffered:
-            depth += 1
         self.fwft = fwft
-        self.buffered = buffered
         self.pipe = pipe
         self.fdepth = depth
         ControlBase.__init__(self, stage, in_multi, stage_ctl)
@@ -774,33 +769,32 @@ class FIFOControl(ControlBase):
 
         # make a FIFO with a signal of equal width to the data_o.
         (fwidth, _) = nmoperator.shape(self.n.data_o)
-        if self.buffered:
-            fifo = SyncFIFOBuffered(fwidth, self.fdepth)
-        else:
-            fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
+        fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
         m.submodules.fifo = fifo
 
-        # store result of processing in combinatorial temporary
-        result = _spec(self.stage.ospec, "r_temp")
-        m.d.comb += nmoperator.eq(result, self.data_r)
-
-        # connect previous rdy/valid/data - do cat on data_i
-        # NOTE: cannot do the PrevControl-looking trick because
-        # of need to process the data.  shaaaame....
-        m.d.comb += [fifo.we.eq(self.p.valid_i_test),
-                     self.p.ready_o.eq(fifo.writable),
-                     nmoperator.eq(fifo.din, nmoperator.cat(result)),
-                   ]
-
-        # connect next rdy/valid/data - do cat on data_o (further below)
-        connections = [self.n.valid_o.eq(fifo.readable),
-                       fifo.re.eq(self.n.ready_i_test),
-                      ]
-        if self.fwft or self.buffered:
-            m.d.comb += connections # combinatorial on next ready/valid
+        def processfn(data_i):
+            # store result of processing in combinatorial temporary
+            result = _spec(self.stage.ospec, "r_temp")
+            m.d.comb += nmoperator.eq(result, self.process(data_i))
+            return nmoperator.cat(result)
+
+        ## prev: make the FIFO (Queue object) "look" like a PrevControl...
+        m.submodules.fp = fp = PrevControl()
+        fp.valid_i, fp._ready_o, fp.data_i = fifo.we, fifo.writable, fifo.din
+        m.d.comb += fp._connect_in(self.p, fn=processfn)
+
+        # next: make the FIFO (Queue object) "look" like a NextControl...
+        m.submodules.fn = fn = NextControl()
+        fn.valid_o, fn.ready_i, fn.data_o  = fifo.readable, fifo.re, fifo.dout
+        connections = fn._connect_out(self.n, fn=nmoperator.cat)
+
+        # ok ok so we can't just do the ready/valid eqs straight:
+        # first 2 from connections are the ready/valid, 3rd is data.
+        if self.fwft:
+            m.d.comb += connections[:2] # combinatorial on next ready/valid
         else:
-            m.d.sync += connections # unbuffered fwft mode needs sync
-        data_o = nmoperator.cat(self.n.data_o).eq(fifo.dout)
+            m.d.sync += connections[:2]  # non-fwft mode needs sync
+        data_o = connections[2] # get the data
         data_o = self._postprocess(data_o) # XXX TBD, does nothing right now
         m.d.comb += data_o