dont need input combinatorial
[ieee754fpu.git] / src / add / example_buf_pipe.py
index 0f2c97462dc3a82fb39f5b4aba20ed02a341ea6b..856fc7ab697ecf1ebbbb1dd726f6eb528f1a5613 100644 (file)
 
 from nmigen import Signal, Cat, Const, Mux, Module
 from nmigen.cli import verilog, rtlil
 
 from nmigen import Signal, Cat, Const, Mux, Module
 from nmigen.cli import verilog, rtlil
+from collections.abc import Sequence
 
 
 
 
-class ExampleStage:
-    """ an example of how to use the buffered pipeline.  actual names of
-        variables (i_data, r_data, o_data, result) below do not matter:
-        the functions however do.
+class IOAckIn:
+
+    def __init__(self):
+        self.p_valid = Signal() # >>in - comes in from PREVIOUS stage
+        self.n_ready = Signal() # in<< - comes in from the NEXT stage
+
+
+class IOAckOut:
+
+    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
+
+
+def eq(o, i):
+    if not isinstance(o, Sequence):
+        o, i = [o], [i]
+    res = []
+    for (ao, ai) in zip(o, i):
+        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.
 
         input data i_data is read (only), is processed and goes into an
         intermediate result store [process()].  this is updated combinatorially.
@@ -66,96 +98,98 @@ class ExampleStage:
         on the next cycle (as long as stall is not raised again) the
         input may begin to be processed and transferred directly to output.
     """
         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):
+        """ pass in a "stage" which may be either a static class or a class
+            instance, which has three functions:
+            * ispec: returns input signals according to the input specification
+            * 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 -+
+        """
+        self.stage = stage
 
 
-    def __init__(self):
-        """ i_data can be a DIFFERENT type from everything else
-            o_data, r_data and result must be of the same type
+        # 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()
+
+    def connect_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),
+               ]
+
+    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),
+               ]
+
+    def connect_out(self, nxt):
+        """ helper function to connect stage to an output source.  do not
+            use to connect stage-to-stage!
         """
         """
-        self.i_data = Signal(16)
-        self.r_data = Signal(16)
-        self.o_data = Signal(16)
-        self.result = Signal(16)
-
-    def process(self):
-        """ process the input data and store it in result.
-            (not needed to be known: result is combinatorial)
+        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),
+               ]
+
+    def set_input(self, i):
+        """ helper function to set the input data
         """
         """
-        return self.result.eq(self.i_data + 1)
+        return eq(self.i.data, i)
 
     def update_buffer(self):
 
     def update_buffer(self):
-        """ copies the result into the intermediate register r_data
+        """ 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 self.r_data.eq(self.result)
+        return eq(self.r_data, self.result)
 
     def update_output(self):
         """ copies the (combinatorial) result into the output
         """
 
     def update_output(self):
         """ copies the (combinatorial) result into the output
         """
-        return self.o_data.eq(self.result)
+        return eq(self.o.data, self.result)
 
     def flush_buffer(self):
         """ copies the *intermediate* register r_data into the output
         """
 
     def flush_buffer(self):
         """ copies the *intermediate* register r_data into the output
         """
-        return self.o_data.eq(self.r_data)
+        return eq(self.o.data, self.r_data)
 
     def ports(self):
 
     def ports(self):
-        return [self.i_data, self.o_data]
-
-class IOAckIn:
-
-    def __init__(self):
-        self.p_valid = Signal()  # >>in - comes in from PREVIOUS stage
-        self.n_ready = Signal() # in<< - comes in from the NEXT stage
-
-
-class IOAckOut:
-
-    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
-
-
-class BufferedPipeline:
-    """ buffered pipeline stage
-
-        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 ---+
-    """
-    def __init__(self):
-        # input: strobe comes in from previous stage, ready comes in from next
-        self.i = IOAckIn()
-        #self.i.p_valid = Signal()    # >>in - comes in from PREVIOUS stage
-        #self.i.n_ready = Signal()   # in<< - comes in from the NEXT stage
-
-        # output: strobe goes out to next stage, ready comes in from previous
-        self.o = IOAckOut()
-        #self.o.n_valid = Signal()    # out>> - goes out to the NEXT stage
-        #self.o.p_ready = Signal()   # <<out - goes out to the PREVIOUS stage
+        return [self.i.data, self.o.data]
 
     def elaborate(self, platform):
         m = Module()
 
         # establish some combinatorial temporaries
 
     def elaborate(self, platform):
         m = Module()
 
         # establish some combinatorial temporaries
-        o_p_readyn = Signal(reset_less=True)
         o_n_validn = Signal(reset_less=True)
         o_n_validn = Signal(reset_less=True)
-        i_n_readyn = Signal(reset_less=True)
         i_p_valid_o_p_ready = Signal(reset_less=True)
         i_p_valid_o_p_ready = Signal(reset_less=True)
-        m.d.comb += [i_n_readyn.eq(~self.i.n_ready),
-                     o_n_validn.eq(~self.o.n_valid),
-                     o_p_readyn.eq(~self.o.p_ready),
+        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),
         ]
 
         # store result of processing in combinatorial temporary
         with m.If(self.i.p_valid): # input is valid: process it
                      i_p_valid_o_p_ready.eq(self.i.p_valid & self.o.p_ready),
         ]
 
         # store result of processing in combinatorial temporary
         with m.If(self.i.p_valid): # input is valid: process it
-            m.d.comb += self.stage.process()
+            m.d.comb += eq(self.result, self.stage.process(self.i.data))
         # if not in stall condition, update the temporary register
         with m.If(self.o.p_ready): # not stalled
         # if not in stall condition, update the temporary register
         with m.If(self.o.p_ready): # not stalled
-            m.d.sync += self.stage.update_buffer()
+            m.d.sync += self.update_buffer()
 
         #with m.If(self.i.p_rst): # reset
         #    m.d.sync += self.o.n_valid.eq(0)
 
         #with m.If(self.i.p_rst): # reset
         #    m.d.sync += self.o.n_valid.eq(0)
@@ -164,12 +198,12 @@ class BufferedPipeline:
             with m.If(self.o.p_ready): # not stalled
                 # nothing in buffer: send (processed) input direct to output
                 m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
             with m.If(self.o.p_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.stage.update_output(),
+                             self.update_output(),
                             ]
             with m.Else(): # o.p_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),
                             ]
             with m.Else(): # o.p_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.stage.flush_buffer(),
+                             self.flush_buffer(),
                              # clear stall condition, declare register empty.
                              self.o.p_ready.eq(1),
                             ]
                              # clear stall condition, declare register empty.
                              self.o.p_ready.eq(1),
                             ]
@@ -180,7 +214,7 @@ class BufferedPipeline:
             m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
                          self.o.p_ready.eq(1), # Keep the buffer empty
                          # set the output data (from comb result)
             m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
                          self.o.p_ready.eq(1), # Keep the buffer empty
                          # set the output data (from comb result)
-                         self.stage.update_output(),
+                         self.update_output(),
                         ]
         # (i.n_ready) false and (o.n_valid) true:
         with m.Elif(i_p_valid_o_p_ready):
                         ]
         # (i.n_ready) false and (o.n_valid) true:
         with m.Elif(i_p_valid_o_p_ready):
@@ -195,18 +229,131 @@ class BufferedPipeline:
                ]
 
 
                ]
 
 
-class BufPipe(BufferedPipeline):
+class ExampleAddStage:
+    """ 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(BufferedPipeline):
+    """ an example of how to use the buffered pipeline, using a class instance
+    """
+
+    def __init__(self):
+        addstage = ExampleAddStage()
+        BufferedPipeline.__init__(self, addstage)
+
+
+class ExampleStage:
+    """ an example of how to use the buffered pipeline, in a static class
+        fashion
+    """
+
+    def ispec():
+        return Signal(16)
+
+    def ospec():
+        return Signal(16)
+
+    def process(i):
+        """ process the input data and returns it (adds 1)
+        """
+        return i + 1
+
+
+class ExampleBufPipe(BufferedPipeline):
+    """ an example of how to use the buffered pipeline.
+    """
 
     def __init__(self):
 
     def __init__(self):
-        BufferedPipeline.__init__(self)
-        self.stage = ExampleStage()
+        BufferedPipeline.__init__(self, ExampleStage)
+
+
+class CombPipe:
+    """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):
+        self.stage = stage
+        self._data_valid = Signal()
+        # 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.ispec() # input type
+        self.o_comb = stage.ospec() # output data
+        self.o.data = stage.ospec() # output type
+        self.o.data.name = "outdata"
+
+    def elaborate(self, platform):
+        m = Module()
+        m.d.comb += eq(self.o_comb, self.stage.process(self.r_data))
+        m.d.comb += self.o.n_valid.eq(self._data_valid)
+        m.d.comb += self.o.p_ready.eq(~self._data_valid | self.i.n_ready)
+        m.d.sync += self._data_valid.eq(self.i.p_valid | \
+                                        (~self.i.n_ready & self._data_valid))
+        with m.If(self.i.p_valid & self.o.p_ready):
+            m.d.sync += eq(self.r_data, self.i.data)
+        m.d.comb += eq(self.o.data, self.o_comb)
+        return m
 
     def ports(self):
 
     def ports(self):
-        return self.stage.ports() + BufferedPipeline.ports(self)
+        return [self.i.data, self.o.data]
+
+
+class ExampleCombPipe(CombPipe):
+    """ an example of how to use the combinatorial pipeline.
+    """
+
+    def __init__(self):
+        CombPipe.__init__(self, ExampleStage)
 
 
 if __name__ == '__main__':
 
 
 if __name__ == '__main__':
-    dut = BufPipe()
+    dut = ExampleBufPipe()
     vl = rtlil.convert(dut, ports=dut.ports())
     with open("test_bufpipe.il", "w") as f:
         f.write(vl)
     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)