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.
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 necessary. 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 -+
"""
- 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):
""" 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
"""
- 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
"""
- return self.o_data.eq(self.r_data)
+ return eq(self.o.data, self.r_data)
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()
# 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
- 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.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),
- self.stage.flush_buffer(),
+ self.flush_buffer(),
# clear stall condition, declare register empty.
self.o.p_ready.eq(1),
]
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):
]
-class ExampleBufPipe(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):
- 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__':
- dut = BufPipe()
+ dut = ExampleBufPipe()
vl = rtlil.convert(dut, ports=dut.ports())
with open("test_bufpipe.il", "w") as f:
f.write(vl)