+ +-- 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.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.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.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.n.o_valid.eq(self.p.i_valid),
+ eq(self.n.o_data, result), # update output
+ ]
+ 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.n.o_valid.eq(1),
+ eq(self.n.o_data, r_data), # flush buffer
+ # clear stall condition, declare register empty.
+ self.p.o_ready.eq(1),
+ ]
+ # ignore input, since p.o_ready is also false.
+
+ # (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.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)
+ eq(self.n.o_data, result),
+ ]
+ # (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.p.o_ready.eq(~(self.p.i_valid & self.n.o_valid))
+
+ return m
+
+
+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