clarify comments
[ieee754fpu.git] / src / add / example_buf_pipe.py
index 8f3742259518750dd93f005276ac4bd1f19e672b..0f4fb20cf952cb23fd53d71022e0e0e136c6ee9b 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,106 +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):
-        """ i_data can be a DIFFERENT type from everything else
-            o_data, r_data and result are best of the same type.
-            however this is not strictly the case.  an intermediate
-            transformation process could hypothetically be applied, however
-            it is result and r_data that definitively need to be of the same
-            (intermediary) type, as it is both result and r_data that
-            are transferred into o_data:
-
-            i_data -> process() -> result --> o_data
+    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 -+
         """
                                      |           ^
                                      |           |
                                      +-> r_data -+
         """
-        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)
+        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()
+
+    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!
+        """
+        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)
@@ -174,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),
                             ]
@@ -190,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):
@@ -205,18 +229,63 @@ 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):
 
     def __init__(self):
-        BufferedPipeline.__init__(self)
-        self.stage = ExampleStage()
+        addstage = ExampleAddStage()
+        BufferedPipeline.__init__(self, addstage)
 
 
-    def ports(self):
-        return self.stage.ports() + BufferedPipeline.ports(self)
+
+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):
+        BufferedPipeline.__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)