-""" Pipeline and BufferedPipeline implementation, conforming to the same API.
+""" Pipeline and BufferedHandshake implementation, conforming to the same API.
For multi-input and multi-output variants, see multipipe.
eq:
------------------
A simple stalling clock-synchronised pipeline that has no buffering
- (unlike BufferedPipeline). Data flows on *every* clock cycle when
+ (unlike BufferedHandshake). Data flows on *every* clock cycle when
the conditions are right (this is nominally when the input is valid
and the output is ready).
A stall anywhere along the line will result in a stall back-propagating
- down the entire chain. The BufferedPipeline by contrast will buffer
+ down the entire chain. The BufferedHandshake by contrast will buffer
incoming data, allowing previous stages one clock cycle's grace before
also having to stall.
logic, if chained together, is *combinatorial*, resulting in
progressively larger gate delay.
+ PassThroughHandshake:
+ ------------------
+
+ A Control class that introduces a single clock delay, passing its
+ data through unaltered. Unlike RegisterPipeline (which relies
+ on UnbufferedPipeline and PassThroughStage) it handles ready/valid
+ itself.
+
RegisterPipeline:
----------------
clock delay, when its stage is a PassThroughStage, it results in a Pipeline
stage that, duh, delays its (unmodified) input by one clock cycle.
- BufferedPipeline:
+ BufferedHandshake:
----------------
nmigen implementation of buffered pipeline stage, based on zipcpu:
dan calls a "buffered handshake".
it's quite a complex state machine!
+
+ SimpleHandshake
+ ---------------
+
+ Synchronised pipeline, Based on:
+ https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
"""
-from nmigen import Signal, Cat, Const, Mux, Module
+from nmigen import Signal, Cat, Const, Mux, Module, Value
from nmigen.cli import verilog, rtlil
+from nmigen.lib.fifo import SyncFIFO
+from nmigen.hdl.ast import ArrayProxy
from nmigen.hdl.rec import Record, Layout
from abc import ABCMeta, abstractmethod
from collections.abc import Sequence
+class RecordObject(Record):
+ def __init__(self, layout=None, name=None):
+ Record.__init__(self, layout=layout or [], name=None)
+
+ def __setattr__(self, k, v):
+ if k in dir(Record) or "fields" not in self.__dict__:
+ return object.__setattr__(self, k, v)
+ self.fields[k] = v
+ if isinstance(v, Record):
+ newlayout = {k: (k, v.layout)}
+ else:
+ newlayout = {k: (k, v.shape())}
+ self.layout.fields.update(newlayout)
+
+
+
class PrevControl:
""" contains signals that come *from* the previous stage (both in and out)
* i_valid: previous stage indicating all incoming data is valid.
* i_data : an input - added by the user of this class
"""
- def __init__(self, i_width=1):
+ def __init__(self, i_width=1, stage_ctl=False):
+ self.stage_ctl = stage_ctl
self.i_valid = Signal(i_width, name="p_i_valid") # prev >>in self
- self.o_ready = Signal(name="p_o_ready") # prev <<out self
+ self._o_ready = Signal(name="p_o_ready") # prev <<out self
self.i_data = None # XXX MUST BE ADDED BY USER
+ if stage_ctl:
+ self.s_o_ready = Signal(name="p_s_o_rdy") # prev <<out self
- def _connect_in(self, prev):
+ @property
+ def o_ready(self):
+ """ public-facing API: indicates (externally) that stage is ready
+ """
+ if self.stage_ctl:
+ return self.s_o_ready # set dynamically by stage
+ return self._o_ready # return this when not under dynamic control
+
+ def _connect_in(self, prev, direct=False, fn=None):
""" internal 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),
+ i_valid = prev.i_valid if direct else prev.i_valid_test
+ i_data = fn(prev.i_data) if fn is not None else prev.i_data
+ return [self.i_valid.eq(i_valid),
prev.o_ready.eq(self.o_ready),
- eq(self.i_data, prev.i_data),
+ eq(self.i_data, i_data),
]
- def i_valid_logic(self):
+ @property
+ def i_valid_test(self):
vlen = len(self.i_valid)
- if vlen > 1: # multi-bit case: valid only when i_valid is all 1s
+ if vlen > 1:
+ # multi-bit case: valid only when i_valid is all 1s
all1s = Const(-1, (len(self.i_valid), False))
- return self.i_valid == all1s
- # single-bit i_valid case
- return self.i_valid
+ i_valid = (self.i_valid == all1s)
+ else:
+ # single-bit i_valid case
+ i_valid = self.i_valid
+
+ # when stage indicates not ready, incoming data
+ # must "appear" to be not ready too
+ if self.stage_ctl:
+ i_valid = i_valid & self.s_o_ready
+
+ return i_valid
class NextControl:
* 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):
+ def __init__(self, stage_ctl=False):
+ self.stage_ctl = stage_ctl
self.o_valid = Signal(name="n_o_valid") # self out>> next
self.i_ready = Signal(name="n_i_ready") # self <<in next
self.o_data = None # XXX MUST BE ADDED BY USER
+ #if self.stage_ctl:
+ self.d_valid = Signal(reset=1) # INTERNAL (data valid)
+
+ @property
+ def i_ready_test(self):
+ if self.stage_ctl:
+ return self.i_ready & self.d_valid
+ return self.i_ready
def connect_to_next(self, nxt):
""" helper function to connect to the next stage data/valid/ready.
eq(nxt.i_data, self.o_data),
]
- def _connect_out(self, nxt):
+ def _connect_out(self, nxt, direct=False, fn=None):
""" internal helper function to connect stage to an output source.
do not use to connect stage-to-stage!
"""
+ i_ready = nxt.i_ready if direct else nxt.i_ready_test
+ o_data = fn(self.o_data) if fn is not None else self.o_data
return [nxt.o_valid.eq(self.o_valid),
- self.i_ready.eq(nxt.i_ready),
- eq(nxt.o_data, self.o_data),
+ self.i_ready.eq(i_ready),
+ eq(nxt.o_data, o_data),
]
-def eq(o, i):
- """ makes signals equal: a helper routine which identifies if it is being
- passed a list (or tuple) of objects, or signals, or Records, and calls
- the objects' eq function.
+class Visitor:
+ """ a helper routine which identifies if it is being passed a list
+ (or tuple) of objects, or signals, or Records, and calls
+ a visitor function.
- complex objects (classes) can be used: they must follow the
- convention of having an eq member function, which takes the
- responsibility of further calling eq and returning a list of
- eq assignments
+ the visiting fn is called when an object is identified.
Record is a special (unusual, recursive) case, where the input may be
specified as a dictionary (which may contain further dictionaries,
the Record's field spec. Alternatively, an object with the same
member names as the Record may be assigned: it does not have to
*be* a Record.
+
+ ArrayProxy is also special-cased, it's a bit messy: whilst ArrayProxy
+ has an eq function, the object being assigned to it (e.g. a python
+ object) might not. despite the *input* having an eq function,
+ that doesn't help us, because it's the *ArrayProxy* that's being
+ assigned to. so.... we cheat. use the ports() function of the
+ python object, enumerate them, find out the list of Signals that way,
+ and assign them.
+ """
+ def visit(self, o, i, act):
+ if isinstance(o, dict):
+ return self.dict_visit(o, i, act)
+
+ res = act.prepare()
+ if not isinstance(o, Sequence):
+ o, i = [o], [i]
+ for (ao, ai) in zip(o, i):
+ #print ("visit", fn, ao, ai)
+ if isinstance(ao, Record):
+ rres = self.record_visit(ao, ai, act)
+ elif isinstance(ao, ArrayProxy) and not isinstance(ai, Value):
+ rres = self.arrayproxy_visit(ao, ai, act)
+ else:
+ rres = act.fn(ao, ai)
+ res += rres
+ return res
+
+ def dict_visit(self, o, i, act):
+ res = act.prepare()
+ for (k, v) in o.items():
+ print ("d-eq", v, i[k])
+ res.append(act.fn(v, i[k]))
+ return res
+
+ def record_visit(self, ao, ai, act):
+ res = act.prepare()
+ for idx, (field_name, field_shape, _) in enumerate(ao.layout):
+ if isinstance(field_shape, Layout):
+ val = ai.fields
+ else:
+ val = ai
+ if hasattr(val, field_name): # check for attribute
+ val = getattr(val, field_name)
+ else:
+ val = val[field_name] # dictionary-style specification
+ val = self.visit(ao.fields[field_name], val, act)
+ if isinstance(val, Sequence):
+ res += val
+ else:
+ res.append(val)
+ return res
+
+ def arrayproxy_visit(self, ao, ai, act):
+ res = act.prepare()
+ for p in ai.ports():
+ op = getattr(ao, p.name)
+ #print (op, p, p.name)
+ res.append(fn(op, p))
+ return res
+
+
+class Eq(Visitor):
+ def __init__(self):
+ self.res = []
+ def prepare(self):
+ return []
+ def fn(self, o, i):
+ rres = o.eq(i)
+ if not isinstance(rres, Sequence):
+ rres = [rres]
+ return rres
+ def __call__(self, o, i):
+ return self.visit(o, i, self)
+
+
+def eq(o, i):
+ """ makes signals equal: a helper routine which identifies if it is being
+ passed a list (or tuple) of objects, or signals, or Records, and calls
+ the objects' eq function.
+ """
+ return Eq()(o, i)
+
+
+def flatten(i):
+ """ flattens a compound structure recursively using Cat
"""
- if not isinstance(o, Sequence):
- o, i = [o], [i]
+ if not isinstance(i, Sequence):
+ i = [i]
res = []
- for (ao, ai) in zip(o, i):
- #print ("eq", ao, ai)
- if isinstance(ao, Record):
- for idx, (field_name, field_shape, _) in enumerate(ao.layout):
+ for ai in i:
+ print ("flatten", ai)
+ if isinstance(ai, Record):
+ print ("record", list(ai.layout))
+ rres = []
+ for idx, (field_name, field_shape, _) in enumerate(ai.layout):
if isinstance(field_shape, Layout):
val = ai.fields
else:
val = getattr(val, field_name)
else:
val = val[field_name] # dictionary-style specification
- rres = eq(ao.fields[field_name], val)
- res += rres
+ print ("recidx", idx, field_name, field_shape, val)
+ val = flatten(val)
+ print ("recidx flat", idx, val)
+ if isinstance(val, Sequence):
+ rres += val
+ else:
+ rres.append(val)
+
+ elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value):
+ rres = []
+ for p in ai.ports():
+ op = getattr(ai, p.name)
+ #print (op, p, p.name)
+ rres.append(flatten(p))
else:
- rres = ao.eq(ai)
- if not isinstance(rres, Sequence):
- rres = [rres]
- res += rres
- return res
+ rres = ai
+ if not isinstance(rres, Sequence):
+ rres = [rres]
+ res += rres
+ print ("flatten res", res)
+ return Cat(*res)
+
class StageCls(metaclass=ABCMeta):
* output of second goes into input into third (etc. etc.)
* the output of this class will be the output of the last stage
"""
- def __init__(self, chain):
+ def __init__(self, chain, specallocate=False):
self.chain = chain
+ self.specallocate = specallocate
def ispec(self):
return self.chain[0].ispec()
def ospec(self):
return self.chain[-1].ospec()
- def setup(self, m, i):
+ def _specallocate_setup(self, m, i):
for (idx, c) in enumerate(self.chain):
if hasattr(c, "setup"):
c.setup(m, i) # stage may have some module stuff
- o = self.chain[idx].ospec() # only the last assignment survives
+ o = self.chain[idx].ospec() # last assignment survives
m.d.comb += eq(o, c.process(i)) # process input into "o"
- if idx != len(self.chain)-1:
- ni = self.chain[idx+1].ispec() # becomes new input on next loop
- m.d.comb += eq(ni, o) # assign output to next input
- i = ni
- self.o = o # last loop is the output
+ if idx == len(self.chain)-1:
+ break
+ i = self.chain[idx+1].ispec() # new input on next loop
+ m.d.comb += eq(i, o) # assign to next input
+ return o # last loop is the output
+
+ def _noallocate_setup(self, m, i):
+ for (idx, c) in enumerate(self.chain):
+ if hasattr(c, "setup"):
+ c.setup(m, i) # stage may have some module stuff
+ i = o = c.process(i) # store input into "o"
+ return o # last loop is the output
+
+ def setup(self, m, i):
+ if self.specallocate:
+ self.o = self._specallocate_setup(m, i)
+ else:
+ self.o = self._noallocate_setup(m, i)
def process(self, i):
return self.o # conform to Stage API: return last-loop output
class ControlBase:
""" Common functions for Pipeline API
"""
- def __init__(self, in_multi=None):
+ def __init__(self, stage=None, in_multi=None, stage_ctl=False):
""" Base class containing ready/valid/data to previous and next stages
* p: contains ready/valid to the previous stage
* n: contains ready/valid to the next stage
- User must also:
+ Except when calling Controlbase.connect(), user must also:
* add i_data member to PrevControl (p) and
* add o_data member to NextControl (n)
"""
+ self.stage = stage
# set up input and output IO ACK (prev/next ready/valid)
- self.p = PrevControl(in_multi)
- self.n = NextControl()
+ self.p = PrevControl(in_multi, stage_ctl)
+ self.n = NextControl(stage_ctl)
+
+ # set up the input and output data
+ if stage is not None:
+ self.p.i_data = stage.ispec() # input type
+ self.n.o_data = stage.ospec()
def connect_to_next(self, nxt):
""" helper function to connect to the next stage data/valid/ready.
"""
return self.n._connect_out(nxt.n)
- def connect(self, m, pipechain):
+ def connect(self, pipechain):
""" connects a chain (list) of Pipeline instances together and
links them to this ControlBase instance:
self.n.o_data = end.stage.ospec()
eqs += end._connect_out(self)
- # activate the assignments
- m.d.comb += eqs
+ return eqs
def set_input(self, i):
""" helper function to set the input data
return eq(self.p.i_data, i)
def ports(self):
- return [self.p.i_valid, self.n.i_ready,
+ res = [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!
]
+ if hasattr(self.p.i_data, "ports"):
+ res += self.p.i_data.ports()
+ else:
+ res += self.p.i_data
+ if hasattr(self.n.o_data, "ports"):
+ res += self.n.o_data.ports()
+ else:
+ res += self.n.o_data
+ return res
+
+ def _elaborate(self, platform):
+ """ handles case where stage has dynamic ready/valid functions
+ """
+ m = Module()
+ if self.stage is not None and hasattr(self.stage, "setup"):
+ self.stage.setup(m, self.p.i_data)
+
+ if not self.p.stage_ctl:
+ return m
+
+ # intercept the previous (outgoing) "ready", combine with stage ready
+ m.d.comb += self.p.s_o_ready.eq(self.p._o_ready & self.stage.d_ready)
-class BufferedPipeline(ControlBase):
+ # intercept the next (incoming) "ready" and combine it with data valid
+ sdv = self.stage.d_valid(self.n.i_ready)
+ m.d.comb += self.n.d_valid.eq(self.n.i_ready & sdv)
+
+ return m
+
+
+class BufferedHandshake(ControlBase):
""" buffered pipeline stage. data and strobe signals travel in sync.
if ever the input is ready and the output is not, processed data
is shunted in a temporary register.
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):
- ControlBase.__init__(self)
- self.stage = 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()
+ self.m = ControlBase._elaborate(self, platform)
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)
+ n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
+ nir_por = Signal(reset_less=True)
+ nir_por_n = Signal(reset_less=True)
p_i_valid = Signal(reset_less=True)
- m.d.comb += [p_i_valid.eq(self.p.i_valid_logic()),
+ nir_novn = Signal(reset_less=True)
+ nirn_novn = Signal(reset_less=True)
+ por_pivn = Signal(reset_less=True)
+ npnn = Signal(reset_less=True)
+ self.m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
o_n_validn.eq(~self.n.o_valid),
- i_p_valid_o_p_ready.eq(p_i_valid & self.p.o_ready),
+ n_i_ready.eq(self.n.i_ready_test),
+ nir_por.eq(n_i_ready & self.p._o_ready),
+ nir_por_n.eq(n_i_ready & ~self.p._o_ready),
+ nir_novn.eq(n_i_ready | o_n_validn),
+ nirn_novn.eq(~n_i_ready & o_n_validn),
+ npnn.eq(nir_por | nirn_novn),
+ por_pivn.eq(self.p._o_ready & ~p_i_valid)
]
# store result of processing in combinatorial temporary
- m.d.comb += eq(result, self.stage.process(self.p.i_data))
+ self.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.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(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), # declare reg empty
- eq(self.n.o_data, r_data), # flush buffer
- self.p.o_ready.eq(1), # clear stall condition
- ]
- # 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(p_i_valid),
- self.p.o_ready.eq(1), # Keep the buffer empty
- eq(self.n.o_data, result), # set output data
+ with self.m.If(self.p.o_ready): # not stalled
+ self.m.d.sync += eq(r_data, result) # update buffer
+
+ # data pass-through conditions
+ with self.m.If(npnn):
+ self.m.d.sync += [self.n.o_valid.eq(p_i_valid), # valid if p_valid
+ eq(self.n.o_data, result), # update output
+ ]
+ # buffer flush conditions (NOTE: can override data passthru conditions)
+ with self.m.If(nir_por_n): # not stalled
+ # Flush the [already processed] buffer to the output port.
+ self.m.d.sync += [self.n.o_valid.eq(1), # reg empty
+ eq(self.n.o_data, r_data), # flush buffer
+ ]
+ # output ready conditions
+ self.m.d.sync += self.p._o_ready.eq(nir_novn | por_pivn)
+
+ return self.m
+
+
+class SimpleHandshake(ControlBase):
+ """ simple handshake control. data and strobe signals travel in sync.
+ implements the protocol used by Wishbone and AXI4.
+
+ Argument: stage. see Stage API above
+
+ 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->--^
+ Truth Table
+
+ Inputs Temporary Output
+ ------- ---------- -----
+ P P N N PiV& ~NiV& N P
+ i o i o PoR NoV o o
+ V R R V V R
+
+ ------- - - - -
+ 0 0 0 0 0 0 >0 0
+ 0 0 0 1 0 1 >1 0
+ 0 0 1 0 0 0 0 1
+ 0 0 1 1 0 0 0 1
+ ------- - - - -
+ 0 1 0 0 0 0 >0 0
+ 0 1 0 1 0 1 >1 0
+ 0 1 1 0 0 0 0 1
+ 0 1 1 1 0 0 0 1
+ ------- - - - -
+ 1 0 0 0 0 0 >0 0
+ 1 0 0 1 0 1 >1 0
+ 1 0 1 0 0 0 0 1
+ 1 0 1 1 0 0 0 1
+ ------- - - - -
+ 1 1 0 0 1 0 1 0
+ 1 1 0 1 1 1 1 0
+ 1 1 1 0 1 0 1 1
+ 1 1 1 1 1 0 1 1
+ ------- - - - -
+ """
+
+ def elaborate(self, platform):
+ self.m = m = ControlBase._elaborate(self, platform)
+
+ r_busy = Signal()
+ result = self.stage.ospec()
+
+ # establish some combinatorial temporaries
+ n_i_ready = Signal(reset_less=True, name="n_i_rdy_data")
+ p_i_valid_p_o_ready = Signal(reset_less=True)
+ p_i_valid = Signal(reset_less=True)
+ m.d.comb += [p_i_valid.eq(self.p.i_valid_test),
+ n_i_ready.eq(self.n.i_ready_test),
+ p_i_valid_p_o_ready.eq(p_i_valid & self.p.o_ready),
+ ]
+
+ # store result of processing in combinatorial temporary
+ m.d.comb += eq(result, self.stage.process(self.p.i_data))
+
+ # previous valid and ready
+ with m.If(p_i_valid_p_o_ready):
+ m.d.sync += [r_busy.eq(1), # output valid
+ eq(self.n.o_data, result), # update output
]
+ # previous invalid or not ready, however next is accepting
+ with m.Elif(n_i_ready):
+ m.d.sync += [eq(self.n.o_data, result)]
+ # TODO: could still send data here (if there was any)
+ #m.d.sync += self.n.o_valid.eq(0) # ...so set output invalid
+ m.d.sync += r_busy.eq(0) # ...so set output invalid
- # (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(~(p_i_valid & self.n.o_valid))
+ m.d.comb += self.n.o_valid.eq(r_busy)
+ # if next is ready, so is previous
+ m.d.comb += self.p._o_ready.eq(n_i_ready)
- return m
+ return self.m
class UnbufferedPipeline(ControlBase):
Note that a stall in one stage will result in the entire pipeline
chain stalling.
- Also that unlike BufferedPipeline, the valid/ready signalling does NOT
+ Also that unlike BufferedHandshake, the valid/ready signalling does NOT
travel synchronously with the data: the valid/ready signalling
combines in a *combinatorial* fashion. Therefore, a long pipeline
chain will lengthen propagation delays.
result: output_shape according to ospec
The output of the combinatorial logic. it is updated
COMBINATORIALLY (no clock dependence).
- """
- def __init__(self, stage):
- ControlBase.__init__(self)
- self.stage = stage
-
- # set up the input and output data
- self.p.i_data = stage.ispec() # input type
- self.n.o_data = stage.ospec() # output type
+ Truth Table
+
+ Inputs Temp Output
+ ------- - -----
+ P P N N ~NiR& N P
+ i o i o NoV o o
+ V R R V V R
+
+ ------- - - -
+ 0 0 0 0 0 0 1
+ 0 0 0 1 1 1 0
+ 0 0 1 0 0 0 1
+ 0 0 1 1 0 0 1
+ ------- - - -
+ 0 1 0 0 0 0 1
+ 0 1 0 1 1 1 0
+ 0 1 1 0 0 0 1
+ 0 1 1 1 0 0 1
+ ------- - - -
+ 1 0 0 0 0 1 1
+ 1 0 0 1 1 1 0
+ 1 0 1 0 0 1 1
+ 1 0 1 1 0 1 1
+ ------- - - -
+ 1 1 0 0 0 1 1
+ 1 1 0 1 1 1 0
+ 1 1 1 0 0 1 1
+ 1 1 1 1 0 1 1
+ ------- - - -
+
+ Note: PoR is *NOT* involved in the above decision-making.
+ """
def elaborate(self, platform):
- m = Module()
+ self.m = m = ControlBase._elaborate(self, platform)
- r_data = self.stage.ispec() # input type
- if hasattr(self.stage, "setup"):
- self.stage.setup(m, r_data)
+ data_valid = Signal() # is data valid or not
+ r_data = self.stage.ospec() # output type
- data_valid = Signal()
+ # some temporaries
p_i_valid = Signal(reset_less=True)
- m.d.comb += p_i_valid.eq(self.p.i_valid_logic())
+ pv = Signal(reset_less=True)
+ m.d.comb += p_i_valid.eq(self.p.i_valid_test)
+ m.d.comb += pv.eq(self.p.i_valid & self.p.o_ready)
+
m.d.comb += self.n.o_valid.eq(data_valid)
- m.d.comb += self.p.o_ready.eq(~data_valid | self.n.i_ready)
+ m.d.comb += self.p._o_ready.eq(~data_valid | self.n.i_ready_test)
m.d.sync += data_valid.eq(p_i_valid | \
- (~self.n.i_ready & 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, self.stage.process(r_data))
- return m
+ (~self.n.i_ready_test & data_valid))
+ with m.If(pv):
+ m.d.sync += eq(r_data, self.stage.process(self.p.i_data))
+ m.d.comb += eq(self.n.o_data, r_data)
+
+ return self.m
+
+
+class UnbufferedPipeline2(ControlBase):
+ """ A simple pipeline stage with single-clock synchronisation
+ and two-way valid/ready synchronised signalling.
+
+ Note that a stall in one stage will result in the entire pipeline
+ chain stalling.
+
+ Also that unlike BufferedHandshake, the valid/ready signalling does NOT
+ travel synchronously with the data: the valid/ready signalling
+ combines in a *combinatorial* fashion. Therefore, a long pipeline
+ chain will lengthen propagation delays.
+
+ Argument: stage. see Stage API, above
+
+ 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-> buf <-+
+ Attributes:
+ -----------
+ p.i_data : StageInput, shaped according to ispec
+ The pipeline input
+ p.o_data : StageOutput, shaped according to ospec
+ The pipeline output
+ buf : output_shape according to ospec
+ A temporary (buffered) copy of a valid output
+ This is HELD if the output is not ready. It is updated
+ SYNCHRONOUSLY.
+ """
+
+ def elaborate(self, platform):
+ self.m = m = ControlBase._elaborate(self, platform)
+
+ buf_full = Signal() # is data valid or not
+ buf = self.stage.ospec() # output type
+
+ # some temporaries
+ p_i_valid = Signal(reset_less=True)
+ m.d.comb += p_i_valid.eq(self.p.i_valid_test)
+
+ m.d.comb += self.n.o_valid.eq(buf_full | p_i_valid)
+ m.d.comb += self.p._o_ready.eq(~buf_full)
+ m.d.sync += buf_full.eq(~self.n.i_ready_test & self.n.o_valid)
+
+ odata = Mux(buf_full, buf, self.stage.process(self.p.i_data))
+ m.d.comb += eq(self.n.o_data, odata)
+ m.d.sync += eq(buf, self.n.o_data)
+
+ return self.m
class PassThroughStage(StageCls):
""" a pass-through stage which has its input data spec equal to its output,
and "passes through" its data from input to output.
"""
- def __init__(self, iospec):
+ def __init__(self, iospecfn):
self.iospecfn = iospecfn
def ispec(self): return self.iospecfn()
def ospec(self): return self.iospecfn()
def process(self, i): return i
+class PassThroughHandshake(ControlBase):
+ """ A control block that delays by one clock cycle.
+ """
+
+ def elaborate(self, platform):
+ self.m = m = ControlBase._elaborate(self, platform)
+
+ # temporaries
+ p_i_valid = Signal(reset_less=True)
+ pvr = Signal(reset_less=True)
+ m.d.comb += p_i_valid.eq(self.p.i_valid_test)
+ m.d.comb += pvr.eq(p_i_valid & self.p.o_ready)
+
+ m.d.comb += self.p.o_ready.eq(~self.n.o_valid | self.n.i_ready_test)
+ m.d.sync += self.n.o_valid.eq(p_i_valid | ~self.p.o_ready)
+
+ odata = Mux(pvr, self.stage.process(self.p.i_data), self.n.o_data)
+ m.d.sync += eq(self.n.o_data, odata)
+
+ return m
+
+
class RegisterPipeline(UnbufferedPipeline):
""" A pipeline stage that delays by one clock cycle, creating a
sync'd latch out of o_data and o_valid as an indirect byproduct
def __init__(self, iospecfn):
UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn))
+
+class FIFOtest(ControlBase):
+ """ A test of using a SyncFIFO to see if it will work.
+ Note: the only things it will accept is a Signal of width "width".
+ """
+
+ def __init__(self, iospecfn, width, depth):
+
+ self.iospecfn = iospecfn
+ self.fwidth = width # XXX temporary
+ self.fdepth = depth
+ #stage = PassThroughStage(iospecfn)
+ ControlBase.__init__(self, stage=self)
+
+ def ispec(self): return self.iospecfn()
+ def ospec(self): return Signal(self.fwidth, name="dout")
+ def process(self, i): return i
+
+ def elaborate(self, platform):
+ self.m = m = ControlBase._elaborate(self, platform)
+
+ (fwidth, _) = self.p.i_data.shape()
+ fifo = SyncFIFO(fwidth, self.fdepth)
+ m.submodules.fifo = fifo
+
+ # XXX TODO: would be nice to do these...
+ ## prev: make the FIFO "look" like a PrevControl...
+ fp = PrevControl()
+ fp.i_valid = fifo.we
+ fp._o_ready = fifo.writable
+ fp.i_data = fifo.din
+ m.d.comb += fp._connect_in(self.p, True, fn=flatten)
+
+ # next: make the FIFO "look" like a NextControl...
+ fn = NextControl()
+ fn.o_valid = fifo.readable
+ fn.i_ready = fifo.re
+ fn.o_data = fifo.dout
+ # ... so we can do this!
+ m.d.comb += fn._connect_out(self.n, fn=flatten)
+
+ # connect previous rdy/valid/data - do flatten on i_data
+ #m.d.comb += [fifo.we.eq(self.p.i_valid_test),
+ # self.p.o_ready.eq(fifo.writable),
+ # eq(fifo.din, flatten(self.p.i_data)),
+ # ]
+
+ # connect next rdy/valid/data - do flatten on o_data
+ #m.d.comb += [self.n.o_valid.eq(fifo.readable),
+ # fifo.re.eq(self.n.i_ready_test),
+ # flatten(self.n.o_data).eq(fifo.dout),
+ # ]
+
+ # err... that should be all!
+ return m
+