""" Pipeline and BufferedHandshake implementation, conforming to the same API.
For multi-input and multi-output variants, see multipipe.
+ Associated development bugs:
+ * http://bugs.libre-riscv.org/show_bug.cgi?id=64
+ * http://bugs.libre-riscv.org/show_bug.cgi?id=57
+
eq:
--
input acceptance conditions are when:
* incoming previous-stage strobe (p.i_valid) is HIGH
- * outgoing previous-stage ready (p.o_ready) is LOW
+ * outgoing previous-stage ready (p.ready_o) is LOW
output transmission conditions are when:
* outgoing next-stage strobe (n.o_valid) is HIGH
https://github.com/ZipCPU/dbgbus/blob/master/hexbus/rtl/hbdeword.v
"""
-from nmigen import Signal, Cat, Const, Mux, Module, Value
+from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
from nmigen.cli import verilog, rtlil
-from nmigen.lib.fifo import SyncFIFO
+from nmigen.lib.fifo import SyncFIFO, SyncFIFOBuffered
from nmigen.hdl.ast import ArrayProxy
from nmigen.hdl.rec import Record, Layout
from abc import ABCMeta, abstractmethod
-from collections.abc import Sequence
+from collections.abc import Sequence, Iterable
+from collections import OrderedDict
+from queue import Queue
+import inspect
+
+
+class Object:
+ def __init__(self):
+ self.fields = OrderedDict()
+
+ def __setattr__(self, k, v):
+ print ("kv", k, v)
+ if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
+ k in dir(Object) or "fields" not in self.__dict__):
+ return object.__setattr__(self, k, v)
+ self.fields[k] = v
+
+ def __getattr__(self, k):
+ if k in self.__dict__:
+ return object.__getattr__(self, k)
+ try:
+ return self.fields[k]
+ except KeyError as e:
+ raise AttributeError(e)
+
+ def __iter__(self):
+ for x in self.fields.values():
+ if isinstance(x, Iterable):
+ yield from x
+ else:
+ yield x
+
+ def eq(self, inp):
+ res = []
+ for (k, o) in self.fields.items():
+ i = getattr(inp, k)
+ print ("eq", o, i)
+ rres = o.eq(i)
+ if isinstance(rres, Sequence):
+ res += rres
+ else:
+ res.append(rres)
+ print (res)
+ return res
+
+ def ports(self):
+ return list(self)
+
+
+class RecordObject(Record):
+ def __init__(self, layout=None, name=None):
+ Record.__init__(self, layout=layout or [], name=None)
+
+ def __setattr__(self, k, v):
+ #print (dir(Record))
+ if (k.startswith('_') or k in ["fields", "name", "src_loc"] or
+ k in dir(Record) or "fields" not in self.__dict__):
+ return object.__setattr__(self, k, v)
+ self.fields[k] = v
+ #print ("RecordObject setattr", k, v)
+ if isinstance(v, Record):
+ newlayout = {k: (k, v.layout)}
+ elif isinstance(v, Value):
+ newlayout = {k: (k, v.shape())}
+ else:
+ newlayout = {k: (k, shape(v))}
+ self.layout.fields.update(newlayout)
+
+ def __iter__(self):
+ for x in self.fields.values():
+ if isinstance(x, Iterable):
+ yield from x
+ else:
+ yield x
+
+ def ports(self):
+ return list(self)
+
+def _spec(fn, name=None):
+ if name is None:
+ return fn()
+ varnames = dict(inspect.getmembers(fn.__code__))['co_varnames']
+ if 'name' in varnames:
+ return fn(name=name)
+ return fn()
-class PrevControl:
+
+class PrevControl(Elaboratable):
""" contains signals that come *from* the previous stage (both in and out)
* i_valid: previous stage indicating all incoming data is valid.
may be a multi-bit signal, where all bits are required
to be asserted to indicate "valid".
- * o_ready: output to next stage indicating readiness to accept data
+ * ready_o: output to next stage indicating readiness to accept data
* i_data : an input - added by the user of this class
"""
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._ready_o = Signal(name="p_ready_o") # 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
+ self.s_ready_o = Signal(name="p_s_o_rdy") # prev <<out self
+ self.trigger = Signal(reset_less=True)
@property
- def o_ready(self):
+ def ready_o(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
+ return self.s_ready_o # set dynamically by stage
+ return self._ready_o # return this when not under dynamic control
- def _connect_in(self, prev, direct=False):
+ 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!
"""
- if direct:
- i_valid = prev.i_valid
- else:
- i_valid = prev.i_valid_test
+ 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),
+ prev.ready_o.eq(self.ready_o),
+ eq(self.i_data, i_data),
]
@property
# 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
+ i_valid = i_valid & self.s_ready_o
return i_valid
+ def elaborate(self, platform):
+ m = Module()
+ m.d.comb += self.trigger.eq(self.i_valid_test & self.ready_o)
+ return m
+
+ def eq(self, i):
+ return [self.i_data.eq(i.i_data),
+ self.ready_o.eq(i.ready_o),
+ self.i_valid.eq(i.i_valid)]
+
+ def __iter__(self):
+ yield self.i_valid
+ yield self.ready_o
+ if hasattr(self.i_data, "ports"):
+ yield from self.i_data.ports()
+ elif isinstance(self.i_data, Sequence):
+ yield from self.i_data
+ else:
+ yield self.i_data
-class NextControl:
+ def ports(self):
+ return list(self)
+
+
+class NextControl(Elaboratable):
""" contains the signals that go *to* the next stage (both in and out)
* o_valid: output indicating to next stage that data is valid
* i_ready: input from next stage indicating that it can accept data
self.o_data = None # XXX MUST BE ADDED BY USER
#if self.stage_ctl:
self.d_valid = Signal(reset=1) # INTERNAL (data valid)
+ self.trigger = Signal(reset_less=True)
@property
def i_ready_test(self):
use this when connecting stage-to-stage
"""
return [nxt.i_valid.eq(self.o_valid),
- self.i_ready.eq(nxt.o_ready),
+ self.i_ready.eq(nxt.ready_o),
eq(nxt.i_data, self.o_data),
]
- def _connect_out(self, nxt, direct=False):
+ 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!
"""
- if direct:
- i_ready = nxt.i_ready
- else:
- i_ready = nxt.i_ready_test
+ i_ready = nxt.i_ready if direct else nxt.i_ready_test
+ o_data = fn(nxt.o_data) if fn is not None else nxt.o_data
return [nxt.o_valid.eq(self.o_valid),
self.i_ready.eq(i_ready),
- eq(nxt.o_data, self.o_data),
+ eq(o_data, self.o_data),
]
+ def elaborate(self, platform):
+ m = Module()
+ m.d.comb += self.trigger.eq(self.i_ready_test & self.o_valid)
+ return m
+
+ def __iter__(self):
+ yield self.i_ready
+ yield self.o_valid
+ if hasattr(self.o_data, "ports"):
+ yield from self.o_data.ports()
+ elif isinstance(self.o_data, Sequence):
+ yield from self.o_data
+ else:
+ yield self.o_data
+
+ def ports(self):
+ return list(self)
-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.
- the visiting fn is called when an object is identified.
+class Visitor2:
+ """ a helper class for iterating twin-argument compound data structures.
Record is a special (unusual, recursive) case, where the input may be
specified as a dictionary (which may contain further dictionaries,
python object, enumerate them, find out the list of Signals that way,
and assign them.
"""
- def visit(self, o, i, act):
+ def iterator2(self, o, i):
if isinstance(o, dict):
- return self.dict_visit(o, i, act)
+ yield from self.dict_iter2(o, i)
- 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)
+ yield from self.record_iter2(ao, ai)
elif isinstance(ao, ArrayProxy) and not isinstance(ai, Value):
- rres = self.arrayproxy_visit(ao, ai, act)
+ yield from self.arrayproxy_iter2(ao, ai)
else:
- rres = act.fn(ao, ai)
- res += rres
- return res
+ yield (ao, ai)
- def dict_visit(self, o, i, act):
- res = act.prepare()
+ def dict_iter2(self, o, i):
for (k, v) in o.items():
- print ("d-eq", v, i[k])
- res.append(act.fn(v, i[k]))
+ print ("d-iter", v, i[k])
+ yield (v, i[k])
return res
- def record_visit(self, ao, ai, act):
- res = act.prepare()
+ def _not_quite_working_with_all_unit_tests_record_iter2(self, ao, ai):
+ print ("record_iter2", ao, ai, type(ao), type(ai))
+ if isinstance(ai, Value):
+ if isinstance(ao, Sequence):
+ ao, ai = [ao], [ai]
+ for o, i in zip(ao, ai):
+ yield (o, i)
+ return
for idx, (field_name, field_shape, _) in enumerate(ao.layout):
if isinstance(field_shape, Layout):
val = ai.fields
val = getattr(val, field_name)
else:
val = val[field_name] # dictionary-style specification
- res += self.visit(ao.fields[field_name], val, act)
- return res
+ yield from self.iterator2(ao.fields[field_name], val)
+
+ def record_iter2(self, ao, ai):
+ 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
+ yield from self.iterator2(ao.fields[field_name], val)
- def arrayproxy_visit(self, ao, ai, act):
- res = act.prepare()
+ def arrayproxy_iter2(self, ao, ai):
for p in ai.ports():
op = getattr(ao, p.name)
- #print (op, p, p.name)
- res.append(fn(op, p))
- return res
+ print ("arrayproxy - p", p, p.name)
+ yield from self.iterator2(op, p)
-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)
+class Visitor:
+ """ a helper class for iterating single-argument compound data structures.
+ similar to Visitor2.
+ """
+ def iterate(self, i):
+ """ iterate a compound structure recursively using yield
+ """
+ if not isinstance(i, Sequence):
+ i = [i]
+ for ai in i:
+ #print ("iterate", ai)
+ if isinstance(ai, Record):
+ #print ("record", list(ai.layout))
+ yield from self.record_iter(ai)
+ elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value):
+ yield from self.array_iter(ai)
+ else:
+ yield ai
+
+ def record_iter(self, ai):
+ for idx, (field_name, field_shape, _) in enumerate(ai.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
+ #print ("recidx", idx, field_name, field_shape, val)
+ yield from self.iterate(val)
+
+ def array_iter(self, ai):
+ for p in ai.ports():
+ yield from self.iterate(p)
def eq(o, i):
passed a list (or tuple) of objects, or signals, or Records, and calls
the objects' eq function.
"""
- return Eq()(o, i)
+ res = []
+ for (ao, ai) in Visitor2().iterator2(o, i):
+ rres = ao.eq(ai)
+ if not isinstance(rres, Sequence):
+ rres = [rres]
+ res += rres
+ return res
+
+
+def shape(i):
+ #print ("shape", i)
+ r = 0
+ for part in list(i):
+ #print ("shape?", part)
+ s, _ = part.shape()
+ r += s
+ return r, False
+
+
+def cat(i):
+ """ flattens a compound structure recursively using Cat
+ """
+ from nmigen.tools import flatten
+ #res = list(flatten(i)) # works (as of nmigen commit f22106e5) HOWEVER...
+ res = list(Visitor().iterate(i)) # needed because input may be a sequence
+ return Cat(*res)
class StageCls(metaclass=ABCMeta):
self.specallocate = specallocate
def ispec(self):
- return self.chain[0].ispec()
+ return _spec(self.chain[0].ispec, "chainin")
def ospec(self):
- return self.chain[-1].ospec()
+ return _spec(self.chain[-1].ospec, "chainout")
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() # last assignment survives
+ ofn = self.chain[idx].ospec # last assignment survives
+ o = _spec(ofn, 'chainin%d' % idx)
m.d.comb += eq(o, c.process(i)) # process input into "o"
if idx == len(self.chain)-1:
break
- i = self.chain[idx+1].ispec() # new input on next loop
+ ifn = self.chain[idx+1].ispec # new input on next loop
+ i = _spec(ifn, 'chainin%d' % (idx+1))
m.d.comb += eq(i, o) # assign to next input
return o # last loop is the output
return self.o # conform to Stage API: return last-loop output
-class ControlBase:
+class ControlBase(Elaboratable):
""" Common functions for Pipeline API
"""
def __init__(self, stage=None, in_multi=None, stage_ctl=False):
# 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()
+ self.p.i_data = _spec(stage.ispec, "i_data") # input type
+ self.n.o_data = _spec(stage.ospec, "o_data") # output type
def connect_to_next(self, nxt):
""" helper function to connect to the next stage data/valid/ready.
# connect front of chain to ourselves
front = pipechain[0]
- self.p.i_data = front.stage.ispec()
+ self.p.i_data = _spec(front.stage.ispec, "chainin")
eqs += front._connect_in(self)
# connect end of chain to ourselves
end = pipechain[-1]
- self.n.o_data = end.stage.ospec()
+ self.n.o_data = _spec(end.stage.ospec, "chainout")
eqs += end._connect_out(self)
return eqs
+ def _postprocess(self, i): # XXX DISABLED
+ return i # RETURNS INPUT
+ if hasattr(self.stage, "postprocess"):
+ return self.stage.postprocess(i)
+ return i
+
def set_input(self, i):
""" helper function to set the input data
"""
return eq(self.p.i_data, i)
+ def __iter__(self):
+ yield from self.p
+ yield from self.n
+
def ports(self):
- res = [self.p.i_valid, self.n.i_ready,
- self.n.o_valid, self.p.o_ready,
- ]
- 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
+ return list(self)
- def _elaborate(self, platform):
+ def elaborate(self, platform):
""" handles case where stage has dynamic ready/valid functions
"""
m = Module()
+ m.submodules.p = self.p
+ m.submodules.n = self.n
if self.stage is not None and hasattr(self.stage, "setup"):
self.stage.setup(m, self.p.i_data)
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)
+ m.d.comb += self.p.s_ready_o.eq(self.p._ready_o & self.stage.d_ready)
# intercept the next (incoming) "ready" and combine it with data valid
sdv = self.stage.d_valid(self.n.i_ready)
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.ready_o <<out stage n.i_ready <<in stage+1
stage-1 p.i_data >>in stage n.o_data out>> stage+1
| |
process --->----^
"""
def elaborate(self, platform):
- self.m = ControlBase._elaborate(self, platform)
+ self.m = ControlBase.elaborate(self, platform)
- result = self.stage.ospec()
- r_data = self.stage.ospec()
+ result = _spec(self.stage.ospec, "r_tmp")
+ r_data = _spec(self.stage.ospec, "r_data")
# establish some combinatorial temporaries
o_n_validn = 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),
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_por.eq(n_i_ready & self.p._ready_o),
+ nir_por_n.eq(n_i_ready & ~self.p._ready_o),
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)
+ por_pivn.eq(self.p._ready_o & ~p_i_valid)
]
# store result of processing in combinatorial temporary
self.m.d.comb += eq(result, self.stage.process(self.p.i_data))
# if not in stall condition, update the temporary register
- with self.m.If(self.p.o_ready): # not stalled
+ with self.m.If(self.p.ready_o): # not stalled
self.m.d.sync += eq(r_data, result) # update buffer
# data pass-through conditions
with self.m.If(npnn):
+ o_data = self._postprocess(result)
self.m.d.sync += [self.n.o_valid.eq(p_i_valid), # valid if p_valid
- eq(self.n.o_data, result), # update output
+ eq(self.n.o_data, o_data), # 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.
+ o_data = self._postprocess(r_data)
self.m.d.sync += [self.n.o_valid.eq(1), # reg empty
- eq(self.n.o_data, r_data), # flush buffer
+ eq(self.n.o_data, o_data), # flush buffer
]
# output ready conditions
- self.m.d.sync += self.p._o_ready.eq(nir_novn | por_pivn)
+ self.m.d.sync += self.p._ready_o.eq(nir_novn | por_pivn)
return self.m
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.ready_o <<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
+ Inputs Temporary Output Data
+ ------- ---------- ----- ----
+ P P N N PiV& ~NiR& 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 0 0 0 0 0 >0 0 reg
+ 0 0 0 1 0 1 >1 0 reg
+ 0 0 1 0 0 0 0 1 process(i_data)
+ 0 0 1 1 0 0 0 1 process(i_data)
------- - - - -
- 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
+ 0 1 0 0 0 0 >0 0 reg
+ 0 1 0 1 0 1 >1 0 reg
+ 0 1 1 0 0 0 0 1 process(i_data)
+ 0 1 1 1 0 0 0 1 process(i_data)
------- - - - -
- 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 0 0 0 0 0 >0 0 reg
+ 1 0 0 1 0 1 >1 0 reg
+ 1 0 1 0 0 0 0 1 process(i_data)
+ 1 0 1 1 0 0 0 1 process(i_data)
------- - - - -
- 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
+ 1 1 0 0 1 0 1 0 process(i_data)
+ 1 1 0 1 1 1 1 0 process(i_data)
+ 1 1 1 0 1 0 1 1 process(i_data)
+ 1 1 1 1 1 0 1 1 process(i_data)
------- - - - -
"""
def elaborate(self, platform):
- self.m = m = ControlBase._elaborate(self, platform)
+ self.m = m = ControlBase.elaborate(self, platform)
r_busy = Signal()
- result = self.stage.ospec()
+ result = _spec(self.stage.ospec, "r_tmp")
# 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_p_ready_o = 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),
+ p_i_valid_p_ready_o.eq(p_i_valid & self.p.ready_o),
]
# 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):
+ with m.If(p_i_valid_p_ready_o):
+ o_data = self._postprocess(result)
m.d.sync += [r_busy.eq(1), # output valid
- eq(self.n.o_data, result), # update output
+ eq(self.n.o_data, o_data), # 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)]
+ o_data = self._postprocess(result)
+ m.d.sync += [eq(self.n.o_data, o_data)]
# 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
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)
+ m.d.comb += self.p._ready_o.eq(n_i_ready)
return self.m
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.ready_o <<out stage n.i_ready <<in stage+1
stage-1 p.i_data >>in stage n.o_data out>> stage+1
| |
r_data result
Truth Table
- Inputs Temp Output
- ------- - -----
+ Inputs Temp Output Data
+ ------- - ----- ----
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 0 0 0 0 0 1 reg
+ 0 0 0 1 1 1 0 reg
+ 0 0 1 0 0 0 1 reg
+ 0 0 1 1 0 0 1 reg
------- - - -
- 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
+ 0 1 0 0 0 0 1 reg
+ 0 1 0 1 1 1 0 reg
+ 0 1 1 0 0 0 1 reg
+ 0 1 1 1 0 0 1 reg
------- - - -
- 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 0 0 0 0 1 1 reg
+ 1 0 0 1 1 1 0 reg
+ 1 0 1 0 0 1 1 reg
+ 1 0 1 1 0 1 1 reg
------- - - -
- 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
+ 1 1 0 0 0 1 1 process(i_data)
+ 1 1 0 1 1 1 0 process(i_data)
+ 1 1 1 0 0 1 1 process(i_data)
+ 1 1 1 1 0 1 1 process(i_data)
------- - - -
Note: PoR is *NOT* involved in the above decision-making.
"""
def elaborate(self, platform):
- self.m = m = ControlBase._elaborate(self, platform)
+ self.m = m = ControlBase.elaborate(self, platform)
data_valid = Signal() # is data valid or not
- r_data = self.stage.ospec() # output type
+ r_data = _spec(self.stage.ospec, "r_tmp") # output type
# some temporaries
p_i_valid = Signal(reset_less=True)
pv = Signal(reset_less=True)
+ buf_full = 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 += pv.eq(self.p.i_valid & self.p.ready_o)
+ m.d.comb += buf_full.eq(~self.n.i_ready_test & data_valid)
m.d.comb += self.n.o_valid.eq(data_valid)
- 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_test & data_valid))
+ m.d.comb += self.p._ready_o.eq(~data_valid | self.n.i_ready_test)
+ m.d.sync += data_valid.eq(p_i_valid | buf_full)
+
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)
+ o_data = self._postprocess(r_data)
+ m.d.comb += eq(self.n.o_data, o_data)
return self.m
-
class UnbufferedPipeline2(ControlBase):
""" A simple pipeline stage with single-clock synchronisation
and two-way valid/ready synchronised signalling.
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.ready_o <<out stage n.i_ready <<in stage+1
stage-1 p.i_data >>in stage n.o_data out>> stage+1
| | |
+- process-> buf <-+
A temporary (buffered) copy of a valid output
This is HELD if the output is not ready. It is updated
SYNCHRONOUSLY.
+
+ Inputs Temp Output Data
+ ------- - -----
+ P P N N ~NiR& N P (buf_full)
+ i o i o NoV o o
+ V R R V V R
+
+ ------- - - -
+ 0 0 0 0 0 0 1 process(i_data)
+ 0 0 0 1 1 1 0 reg (odata, unchanged)
+ 0 0 1 0 0 0 1 process(i_data)
+ 0 0 1 1 0 0 1 process(i_data)
+ ------- - - -
+ 0 1 0 0 0 0 1 process(i_data)
+ 0 1 0 1 1 1 0 reg (odata, unchanged)
+ 0 1 1 0 0 0 1 process(i_data)
+ 0 1 1 1 0 0 1 process(i_data)
+ ------- - - -
+ 1 0 0 0 0 1 1 process(i_data)
+ 1 0 0 1 1 1 0 reg (odata, unchanged)
+ 1 0 1 0 0 1 1 process(i_data)
+ 1 0 1 1 0 1 1 process(i_data)
+ ------- - - -
+ 1 1 0 0 0 1 1 process(i_data)
+ 1 1 0 1 1 1 0 reg (odata, unchanged)
+ 1 1 1 0 0 1 1 process(i_data)
+ 1 1 1 1 0 1 1 process(i_data)
+ ------- - - -
+
+ Note: PoR is *NOT* involved in the above decision-making.
"""
def elaborate(self, platform):
- self.m = m = ControlBase._elaborate(self, platform)
+ self.m = m = ControlBase.elaborate(self, platform)
buf_full = Signal() # is data valid or not
- buf = self.stage.ospec() # output type
+ buf = _spec(self.stage.ospec, "r_tmp") # 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.comb += self.p._ready_o.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)
+ o_data = Mux(buf_full, buf, self.stage.process(self.p.i_data))
+ o_data = self._postprocess(o_data)
+ m.d.comb += eq(self.n.o_data, o_data)
m.d.sync += eq(buf, self.n.o_data)
return self.m
class PassThroughHandshake(ControlBase):
""" A control block that delays by one clock cycle.
+
+ Inputs Temporary Output Data
+ ------- ------------------ ----- ----
+ P P N N PiV& PiV| NiR| pvr N P (pvr)
+ i o i o PoR ~PoR ~NoV o o
+ V R R V V R
+
+ ------- - - - - - -
+ 0 0 0 0 0 1 1 0 1 1 odata (unchanged)
+ 0 0 0 1 0 1 0 0 1 0 odata (unchanged)
+ 0 0 1 0 0 1 1 0 1 1 odata (unchanged)
+ 0 0 1 1 0 1 1 0 1 1 odata (unchanged)
+ ------- - - - - - -
+ 0 1 0 0 0 0 1 0 0 1 odata (unchanged)
+ 0 1 0 1 0 0 0 0 0 0 odata (unchanged)
+ 0 1 1 0 0 0 1 0 0 1 odata (unchanged)
+ 0 1 1 1 0 0 1 0 0 1 odata (unchanged)
+ ------- - - - - - -
+ 1 0 0 0 0 1 1 1 1 1 process(in)
+ 1 0 0 1 0 1 0 0 1 0 odata (unchanged)
+ 1 0 1 0 0 1 1 1 1 1 process(in)
+ 1 0 1 1 0 1 1 1 1 1 process(in)
+ ------- - - - - - -
+ 1 1 0 0 1 1 1 1 1 1 process(in)
+ 1 1 0 1 1 1 0 0 1 0 odata (unchanged)
+ 1 1 1 0 1 1 1 1 1 1 process(in)
+ 1 1 1 1 1 1 1 1 1 1 process(in)
+ ------- - - - - - -
+
"""
def elaborate(self, platform):
- self.m = m = ControlBase._elaborate(self, platform)
+ self.m = m = ControlBase.elaborate(self, platform)
+
+ r_data = _spec(self.stage.ospec, "r_tmp") # output type
# 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 += pvr.eq(p_i_valid & self.p.ready_o)
- 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)
+ m.d.comb += self.p.ready_o.eq(~self.n.o_valid | self.n.i_ready_test)
+ m.d.sync += self.n.o_valid.eq(p_i_valid | ~self.p.ready_o)
- odata = Mux(pvr, self.stage.process(self.p.i_data), self.n.o_data)
- m.d.sync += eq(self.n.o_data, odata)
+ odata = Mux(pvr, self.stage.process(self.p.i_data), r_data)
+ m.d.sync += eq(r_data, odata)
+ r_data = self._postprocess(r_data)
+ m.d.comb += eq(self.n.o_data, r_data)
return m
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".
+class FIFOControl(ControlBase):
+ """ FIFO Control. Uses SyncFIFO to store data, coincidentally
+ happens to have same valid/ready signalling as Stage API.
+
+ i_data -> fifo.din -> FIFO -> fifo.dout -> o_data
"""
- def __init__(self, width, depth):
+ def __init__(self, depth, stage, in_multi=None, stage_ctl=False,
+ fwft=True, buffered=False, pipe=False):
+ """ FIFO Control
+
+ * depth: number of entries in the FIFO
+ * stage: data processing block
+ * fwft : first word fall-thru mode (non-fwft introduces delay)
+ * buffered: use buffered FIFO (introduces extra cycle delay)
+
+ NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO
+ (fwft=True, buffered=False)
+
+ NOTE 2: i_data *must* have a shape function. it can therefore
+ be a Signal, or a Record, or a RecordObject.
+
+ data is processed (and located) as follows:
+
+ self.p self.stage temp fn temp fn temp fp self.n
+ i_data->process()->result->cat->din.FIFO.dout->cat(o_data)
- self.fwidth = width
+ yes, really: cat produces a Cat() which can be assigned to.
+ this is how the FIFO gets de-catted without needing a de-cat
+ function
+ """
+
+ assert not (fwft and buffered), "buffered cannot do fwft"
+ if buffered:
+ depth += 1
+ self.fwft = fwft
+ self.buffered = buffered
+ self.pipe = pipe
self.fdepth = depth
- def iospecfn():
- return Signal(width, name="data")
- stage = PassThroughStage(iospecfn)
- ControlBase.__init__(self, stage=stage)
+ ControlBase.__init__(self, stage, in_multi, stage_ctl)
def elaborate(self, platform):
- self.m = m = ControlBase._elaborate(self, platform)
+ self.m = m = ControlBase.elaborate(self, platform)
- fifo = SyncFIFO(self.fwidth, self.fdepth)
+ # make a FIFO with a signal of equal width to the o_data.
+ (fwidth, _) = shape(self.n.o_data)
+ if self.buffered:
+ fifo = SyncFIFOBuffered(fwidth, self.fdepth)
+ else:
+ fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
m.submodules.fifo = fifo
- # 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
- # ... so we can do this!
- m.d.comb += fp._connect_in(self.p, True)
-
- # 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)
-
- # err... that should be all!
+ # store result of processing in combinatorial temporary
+ result = _spec(self.stage.ospec, "r_temp")
+ m.d.comb += eq(result, self.stage.process(self.p.i_data))
+
+ # connect previous rdy/valid/data - do cat on i_data
+ # NOTE: cannot do the PrevControl-looking trick because
+ # of need to process the data. shaaaame....
+ m.d.comb += [fifo.we.eq(self.p.i_valid_test),
+ self.p.ready_o.eq(fifo.writable),
+ eq(fifo.din, cat(result)),
+ ]
+
+ # connect next rdy/valid/data - do cat on o_data
+ connections = [self.n.o_valid.eq(fifo.readable),
+ fifo.re.eq(self.n.i_ready_test),
+ ]
+ if self.fwft or self.buffered:
+ m.d.comb += connections
+ else:
+ m.d.sync += connections # unbuffered fwft mode needs sync
+ o_data = cat(self.n.o_data).eq(fifo.dout)
+ o_data = self._postprocess(o_data)
+ m.d.comb += o_data
+
return m
+
+# aka "RegStage".
+class UnbufferedPipeline(FIFOControl):
+ def __init__(self, stage, in_multi=None, stage_ctl=False):
+ FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
+ fwft=True, pipe=False)
+
+# aka "BreakReadyStage" XXX had to set fwft=True to get it to work
+class PassThroughHandshake(FIFOControl):
+ def __init__(self, stage, in_multi=None, stage_ctl=False):
+ FIFOControl.__init__(self, 1, stage, in_multi, stage_ctl,
+ fwft=True, pipe=True)
+
+# this is *probably* BufferedHandshake, although test #997 now succeeds.
+class BufferedHandshake(FIFOControl):
+ def __init__(self, stage, in_multi=None, stage_ctl=False):
+ FIFOControl.__init__(self, 2, stage, in_multi, stage_ctl,
+ fwft=True, pipe=False)
+
+
+"""
+# this is *probably* SimpleHandshake (note: memory cell size=0)
+class SimpleHandshake(FIFOControl):
+ def __init__(self, stage, in_multi=None, stage_ctl=False):
+ FIFOControl.__init__(self, 0, stage, in_multi, stage_ctl,
+ fwft=True, pipe=False)
+"""