update comments
[ieee754fpu.git] / src / add / example_buf_pipe.py
index 0f4fb20cf952cb23fd53d71022e0e0e136c6ee9b..a1f5d04f565c1ec3cd1d2f813ca3fd9436fa6216 100644 (file)
     where data will flow on *every* clock when the conditions are right.
 
     input acceptance conditions are when:
-        * incoming previous-stage strobe (i.p_valid) is HIGH
-        * outgoing previous-stage ready   (o.p_ready) is LOW
+        * incoming previous-stage strobe (p.i_valid) is HIGH
+        * outgoing previous-stage ready   (p.o_ready) is LOW
 
     output transmission conditions are when:
-        * outgoing next-stage strobe (o.n_valid) is HIGH
-        * outgoing next-stage ready   (i.n_ready) is LOW
+        * outgoing next-stage strobe (n.o_valid) is HIGH
+        * outgoing next-stage ready   (n.i_ready) is LOW
 
     the tricky bit is when the input has valid data and the output is not
     ready to accept it.  if it wasn't for the clock synchronisation, it
 
 from nmigen import Signal, Cat, Const, Mux, Module
 from nmigen.cli import verilog, rtlil
+from nmigen.hdl.rec import Record, Layout
+
 from collections.abc import Sequence
 
 
-class IOAckIn:
+class PrevControl:
+    """ contains signals that come *from* the previous stage (both in and out)
+        * i_valid: input from previous stage indicating incoming data is valid
+        * o_ready: output to next stage indicating readiness to accept data
+        * i_data : an input - added by the user of this class
+    """
 
     def __init__(self):
-        self.p_valid = Signal() # >>in - comes in from PREVIOUS stage
-        self.n_ready = Signal() # in<< - comes in from the NEXT stage
+        self.i_valid = Signal(name="p_i_valid") # prev   >>in  self
+        self.o_ready = Signal(name="p_o_ready") # prev   <<out self
 
+    def connect_in(self, prev):
+        """ helper function to connect stage to an input source.  do not
+            use to connect stage-to-stage!
+        """
+        return [self.i_valid.eq(prev.i_valid),
+                prev.o_ready.eq(self.o_ready),
+                eq(self.i_data, prev.i_data),
+               ]
 
-class IOAckOut:
 
+class NextControl:
+    """ contains the signals that go *to* the next stage (both in and out)
+        * o_valid: output indicating to next stage that data is valid
+        * i_ready: input from next stage indicating that it can accept data
+        * o_data : an output - added by the user of this class
+    """
     def __init__(self):
-        self.n_valid = Signal() # out>> - goes out to the NEXT stage
-        self.p_ready = Signal() # <<out - goes out to the PREVIOUS stage
+        self.o_valid = Signal(name="n_o_valid") # self out>>  next
+        self.i_ready = Signal(name="n_i_ready") # self <<in   next
+
+    def connect_to_next(self, nxt):
+        """ helper function to connect to the next stage data/valid/ready.
+            data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
+        """
+        return [nxt.i_valid.eq(self.o_valid),
+                self.i_ready.eq(nxt.o_ready),
+                eq(nxt.i_data, self.o_data),
+               ]
+
+    def connect_out(self, nxt):
+        """ helper function to connect stage to an output source.  do not
+            use to connect stage-to-stage!
+        """
+        return [nxt.o_valid.eq(self.o_valid),
+                self.i_ready.eq(nxt.i_ready),
+                eq(nxt.o_data, self.o_data),
+               ]
 
 
 def eq(o, i):
+    """ makes signals equal: a helper routine which identifies if it is being
+        passsed a list (or tuple) of objects, and calls the objects' eq
+        function.
+
+        Record is a special (unusual, recursive) case, where the input
+        is specified as a dictionary (which may contain further dictionaries,
+        recursively), where the field names of the dictionary must match
+        the Record's field spec.
+    """
     if not isinstance(o, Sequence):
         o, i = [o], [i]
     res = []
     for (ao, ai) in zip(o, i):
-        res.append(ao.eq(ai))
+        #print ("eq", ao, ai)
+        if isinstance(ao, Record):
+            for idx, (field_name, field_shape, _) in enumerate(ao.layout):
+                if isinstance(field_shape, Layout):
+                    rres = eq(ao.fields[field_name], ai.fields[field_name])
+                else:
+                    rres = eq(ao.fields[field_name], ai[field_name])
+                res += rres
+        else:
+            res.append(ao.eq(ai))
     return res
 
 
-class BufferedPipeline:
-    """ buffered pipeline stage.  data and strobe signals travel in sync.
-        if ever the input is ready and the output is not, processed data
-        is stored in a temporary register.
-
-        stage-1   i.p_valid >>in   stage   o.n_valid out>>   stage+1
-        stage-1   o.p_ready <<out  stage   i.n_ready <<in    stage+1
-        stage-1   i.data    >>in   stage   o.data    out>>   stage+1
-                              |             |
-                            process --->----^
-                              |             |
-                              +-- r_data ->-+
-
-        input data i_data is read (only), is processed and goes into an
-        intermediate result store [process()].  this is updated combinatorially.
-
-        in a non-stall condition, the intermediate result will go into the
-        output (update_output).  however if ever there is a stall, it goes
-        into r_data instead [update_buffer()].
-
-        when the non-stall condition is released, r_data is the first
-        to be transferred to the output [flush_buffer()], and the stall
-        condition cleared.
-
-        on the next cycle (as long as stall is not raised again) the
-        input may begin to be processed and transferred directly to output.
+class PipelineBase:
+    """ Common functions for Pipeline API
     """
     def __init__(self, stage):
         """ pass in a "stage" which may be either a static class or a class
@@ -105,129 +137,133 @@ class BufferedPipeline:
             * ispec: returns output signals to the output specification
             * process: takes an input instance and returns processed data
 
-            i_data -> process() -> result --> o.data
-                                     |           ^
-                                     |           |
-                                     +-> r_data -+
+            User must also:
+            * add i_data member to PrevControl and
+            * add o_data member to NextControl
         """
         self.stage = stage
 
         # set up input and output IO ACK (prev/next ready/valid)
-        self.i = IOAckIn()
-        self.o = IOAckOut()
-
-        # set up the input and output data
-        self.i.data = stage.ispec() # input type
-        self.r_data = stage.ospec() # all these are output type
-        self.result = stage.ospec()
-        self.o.data = stage.ospec()
+        self.p = PrevControl()
+        self.n = NextControl()
 
-    def connect_next(self, nxt):
+    def connect_to_next(self, nxt):
         """ helper function to connect to the next stage data/valid/ready.
-            data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
         """
-        return [nxt.i.p_valid.eq(self.o.n_valid),
-                self.i.n_ready.eq(nxt.o.p_ready),
-                eq(nxt.i.data, self.o.data),
-               ]
+        return self.n.connect_to_next(nxt.p)
 
     def connect_in(self, prev):
         """ helper function to connect stage to an input source.  do not
             use to connect stage-to-stage!
         """
-        return [self.i.p_valid.eq(prev.i.p_valid),
-                prev.o.p_ready.eq(self.o.p_ready),
-                eq(self.i.data, prev.i.data),
-               ]
+        return self.p.connect_in(prev.p)
 
     def connect_out(self, nxt):
         """ helper function to connect stage to an output source.  do not
             use to connect stage-to-stage!
         """
-        return [nxt.o.n_valid.eq(self.o.n_valid),
-                self.i.n_ready.eq(nxt.i.n_ready),
-                eq(nxt.o.data, self.o.data),
-               ]
+        return self.n.connect_out(nxt.n)
 
     def set_input(self, i):
         """ helper function to set the input data
         """
-        return eq(self.i.data, i)
+        return eq(self.p.i_data, i)
 
-    def update_buffer(self):
-        """ copies the result into the intermediate register r_data,
-            which will need to be outputted on a subsequent cycle
-            prior to allowing "normal" operation.
-        """
-        return eq(self.r_data, self.result)
+    def ports(self):
+        return [self.p.i_valid, self.n.i_ready,
+                self.n.o_valid, self.p.o_ready,
+                self.p.i_data, self.n.o_data   # XXX need flattening!
+               ]
 
-    def update_output(self):
-        """ copies the (combinatorial) result into the output
-        """
-        return eq(self.o.data, self.result)
 
-    def flush_buffer(self):
-        """ copies the *intermediate* register r_data into the output
-        """
-        return eq(self.o.data, self.r_data)
+class BufferedPipeline(PipelineBase):
+    """ buffered pipeline stage.  data and strobe signals travel in sync.
+        if ever the input is ready and the output is not, processed data
+        is stored in a temporary register.
 
-    def ports(self):
-        return [self.i.data, self.o.data]
+        stage-1   p.i_valid >>in   stage   n.o_valid out>>   stage+1
+        stage-1   p.o_ready <<out  stage   n.i_ready <<in    stage+1
+        stage-1   p.i_data  >>in   stage   n.o_data  out>>   stage+1
+                              |             |
+                            process --->----^
+                              |             |
+                              +-- r_data ->-+
+
+        input data p.i_data is read (only), is processed and goes into an
+        intermediate result store [process()].  this is updated combinatorially.
+
+        in a non-stall condition, the intermediate result will go into the
+        output (update_output).  however if ever there is a stall, it goes
+        into r_data instead [update_buffer()].
+
+        when the non-stall condition is released, r_data is the first
+        to be transferred to the output [flush_buffer()], and the stall
+        condition cleared.
+
+        on the next cycle (as long as stall is not raised again) the
+        input may begin to be processed and transferred directly to output.
+    """
+    def __init__(self, stage):
+        PipelineBase.__init__(self, stage)
+
+        # set up the input and output data
+        self.p.i_data = stage.ispec() # input type
+        self.n.o_data = stage.ospec()
 
     def elaborate(self, platform):
         m = Module()
 
+        result = self.stage.ospec()
+        r_data = self.stage.ospec()
+        if hasattr(self.stage, "setup"):
+            self.stage.setup(m, self.p.i_data)
+
         # establish some combinatorial temporaries
         o_n_validn = Signal(reset_less=True)
         i_p_valid_o_p_ready = Signal(reset_less=True)
-        m.d.comb += [o_n_validn.eq(~self.o.n_valid),
-                     i_p_valid_o_p_ready.eq(self.i.p_valid & self.o.p_ready),
+        m.d.comb += [o_n_validn.eq(~self.n.o_valid),
+                     i_p_valid_o_p_ready.eq(self.p.i_valid & self.p.o_ready),
         ]
 
         # store result of processing in combinatorial temporary
-        with m.If(self.i.p_valid): # input is valid: process it
-            m.d.comb += eq(self.result, self.stage.process(self.i.data))
+        #with m.If(self.p.i_valid): # input is valid: process it
+        m.d.comb += eq(result, self.stage.process(self.p.i_data))
         # if not in stall condition, update the temporary register
-        with m.If(self.o.p_ready): # not stalled
-            m.d.sync += self.update_buffer()
-
-        #with m.If(self.i.p_rst): # reset
-        #    m.d.sync += self.o.n_valid.eq(0)
-        #    m.d.sync += self.o.p_ready.eq(0)
-        with m.If(self.i.n_ready): # next stage is ready
-            with m.If(self.o.p_ready): # not stalled
+        with m.If(self.p.o_ready): # not stalled
+            m.d.sync += eq(r_data, result) # update buffer
+
+        #with m.If(self.p.i_rst): # reset
+        #    m.d.sync += self.n.o_valid.eq(0)
+        #    m.d.sync += self.p.o_ready.eq(0)
+        with m.If(self.n.i_ready): # next stage is ready
+            with m.If(self.p.o_ready): # not stalled
                 # nothing in buffer: send (processed) input direct to output
-                m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
-                             self.update_output(),
+                m.d.sync += [self.n.o_valid.eq(self.p.i_valid),
+                             eq(self.n.o_data, result), # update output
                             ]
-            with m.Else(): # o.p_ready is false, and something is in buffer.
+            with m.Else(): # p.o_ready is false, and something is in buffer.
                 # Flush the [already processed] buffer to the output port.
-                m.d.sync += [self.o.n_valid.eq(1),
-                             self.flush_buffer(),
+                m.d.sync += [self.n.o_valid.eq(1),
+                             eq(self.n.o_data, r_data), # flush buffer
                              # clear stall condition, declare register empty.
-                             self.o.p_ready.eq(1),
+                             self.p.o_ready.eq(1),
                             ]
-                # ignore input, since o.p_ready is also false.
+                # ignore input, since p.o_ready is also false.
 
-        # (i.n_ready) is false here: next stage is ready
+        # (n.i_ready) is false here: next stage is ready
         with m.Elif(o_n_validn): # next stage being told "ready"
-            m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
-                         self.o.p_ready.eq(1), # Keep the buffer empty
+            m.d.sync += [self.n.o_valid.eq(self.p.i_valid),
+                         self.p.o_ready.eq(1), # Keep the buffer empty
                          # set the output data (from comb result)
-                         self.update_output(),
+                         eq(self.n.o_data, result),
                         ]
-        # (i.n_ready) false and (o.n_valid) true:
+        # (n.i_ready) false and (n.o_valid) true:
         with m.Elif(i_p_valid_o_p_ready):
             # If next stage *is* ready, and not stalled yet, accept input
-            m.d.sync += self.o.p_ready.eq(~(self.i.p_valid & self.o.n_valid))
+            m.d.sync += self.p.o_ready.eq(~(self.p.i_valid & self.n.o_valid))
 
         return m
 
-    def ports(self):
-        return [self.i.p_valid, self.i.n_ready,
-                self.o.n_valid, self.o.p_ready,
-               ]
-
 
 class ExampleAddStage:
     """ an example of how to use the buffered pipeline, as a class instance
@@ -284,8 +320,73 @@ class ExampleBufPipe(BufferedPipeline):
         BufferedPipeline.__init__(self, ExampleStage)
 
 
+class CombPipe(PipelineBase):
+    """A simple pipeline stage containing combinational logic that can execute
+    completely in one clock cycle.
+
+    Parameters:
+    -----------
+    input_shape : int or tuple or None
+        the shape of ``input.data`` and ``comb_input``
+    output_shape : int or tuple or None
+        the shape of ``output.data`` and ``comb_output``
+    name : str
+        the name
+
+    Attributes:
+    -----------
+    input : StageInput
+        The pipeline input
+    output : StageOutput
+        The pipeline output
+    comb_input : Signal, input_shape
+        The input to the combinatorial logic
+    comb_output: Signal, output_shape
+        The output of the combinatorial logic
+    """
+
+    def __init__(self, stage):
+        PipelineBase.__init__(self, stage)
+        self._data_valid = Signal()
+
+        # set up the input and output data
+        self.p.i_data = stage.ispec() # input type
+        self.n.o_data = stage.ospec() # output type
+
+    def elaborate(self, platform):
+        m = Module()
+
+        r_data = self.stage.ispec() # input type
+        result = self.stage.ospec() # output data
+        if hasattr(self.stage, "setup"):
+            self.stage.setup(m, r_data)
+
+        m.d.comb += eq(result, self.stage.process(r_data))
+        m.d.comb += self.n.o_valid.eq(self._data_valid)
+        m.d.comb += self.p.o_ready.eq(~self._data_valid | self.n.i_ready)
+        m.d.sync += self._data_valid.eq(self.p.i_valid | \
+                                        (~self.n.i_ready & self._data_valid))
+        with m.If(self.p.i_valid & self.p.o_ready):
+            m.d.sync += eq(r_data, self.p.i_data)
+        m.d.comb += eq(self.n.o_data, result)
+        return m
+
+
+class ExampleCombPipe(CombPipe):
+    """ an example of how to use the combinatorial pipeline.
+    """
+
+    def __init__(self):
+        CombPipe.__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 = ExampleCombPipe()
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_combpipe.il", "w") as f:
+        f.write(vl)