""" 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:
--
from nmigen import Signal, Cat, Const, Mux, Module, Value
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 queue import Queue
class RecordObject(Record):
newlayout = {k: (k, v.shape())}
self.layout.fields.update(newlayout)
+ def __iter__(self):
+ for x in self.fields.values():
+ yield x
class PrevControl:
]
-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 record_iter2(self, ao, ai):
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
- val = self.visit(ao.fields[field_name], val, act)
- if isinstance(val, Sequence):
- res += val
- else:
- res.append(val)
- return res
+ 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 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
-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 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)
-
-
-def flatten(i):
- """ flattens a compound structure recursively using Cat
- """
- if not isinstance(i, Sequence):
- i = [i]
res = []
- 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 = 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)
- 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 = ai
+ for (ao, ai) in Visitor2().iterator2(o, i):
+ rres = ao.eq(ai)
if not isinstance(rres, Sequence):
rres = [rres]
res += rres
- print ("flatten res", res)
- return Cat(*res)
+ return res
+
+
+
+def cat(i):
+ """ flattens a compound structure recursively using Cat
+
+ NOTE: this does NOT work:
+ from nmigen.tools import flatten
+ res = list(flatten(i))
+
+ the reason is that flatten is not sophisticated enough,
+ and does not support iteration on Record:
+ File "nmigen/tools.py", line 12, in flatten
+ for e in i:
+ File "nmigen/hdl/rec.py", line 98, in __getitem__
+ .format(reference, name, ", ".join(self.fields))) from None
+ NameError: Unnamed record does not have a field '0'.
+ Did you mean one of: sig1, r2, r3?
+ """
+ #from nmigen.tools import flatten
+ #res = list(flatten(i))
+ res = list(Visitor().iterate(i))
+ return Cat(*res)
class StageCls(metaclass=ABCMeta):
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
"""
# 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)
+--process->--^
Truth Table
- Inputs Temporary Output
- ------- ---------- -----
+ 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)
------- - - - -
"""
# previous valid and ready
with m.If(p_i_valid_p_o_ready):
+ 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
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.
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.
This is HELD if the output is not ready. It is updated
SYNCHRONOUSLY.
- Inputs Temp Output
+ Inputs Temp Output Data
------- - -----
- P P N N ~NiR& N P
+ 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
- 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 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
- 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 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
- 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 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
- 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 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.
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)
+ 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
V R R V V R
------- - - - - - -
- 0 0 0 0 0 1 1 0 1 1 reg
- 0 0 0 1 0 1 0 0 1 0 reg
- 0 0 1 0 0 1 1 0 1 1 reg
- 0 0 1 1 0 1 1 0 1 1 reg
+ 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 reg
- 0 1 0 1 0 0 0 0 0 0 reg
- 0 1 1 0 0 0 1 0 0 1 reg
- 0 1 1 1 0 0 1 0 0 1 reg
+ 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 reg
+ 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 reg
+ 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)
+ r_data = self.stage.ospec() # output type
+
# temporaries
p_i_valid = Signal(reset_less=True)
pvr = Signal(reset_less=True)
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)
+ 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
i_data -> fifo.din -> FIFO -> fifo.dout -> o_data
"""
- def __init__(self, depth, stage):
- """ * iospecfn: specification for incoming and outgoing data
- * depth : number of entries in the FIFO
+ 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->flatten->din.FIFO.dout->flatten(o_data)
+ i_data->process()->result->cat->din.FIFO.dout->cat(o_data)
- yes, really: flatten produces a Cat() which can be assigned to.
- this is how the FIFO gets de-flattened without needing a de-flatten
+ 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
- ControlBase.__init__(self, stage=stage)
+ ControlBase.__init__(self, stage, in_multi, stage_ctl)
def elaborate(self, platform):
self.m = m = ControlBase._elaborate(self, platform)
# make a FIFO with a signal of equal width to the o_data.
(fwidth, _) = self.n.o_data.shape()
- fifo = SyncFIFO(fwidth, self.fdepth)
+ if self.buffered:
+ fifo = SyncFIFOBuffered(fwidth, self.fdepth)
+ else:
+ fifo = Queue(fwidth, self.fdepth, fwft=self.fwft, pipe=self.pipe)
m.submodules.fifo = fifo
# store result of processing in combinatorial temporary
result = self.stage.ospec()
m.d.comb += eq(result, self.stage.process(self.p.i_data))
- # connect previous rdy/valid/data - do flatten on 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.o_ready.eq(fifo.writable),
- eq(fifo.din, flatten(result)),
+ eq(fifo.din, cat(result)),
]
- # next: make the FIFO "look" like a NextControl...
- fn = NextControl()
- fn.o_valid, fn.i_ready, fn.o_data = fifo.readable, fifo.re, fifo.dout
- m.d.comb += fn._connect_out(self.n, fn=flatten) # ...so we can do this!
+ # 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
- # err... that should be all!
return m
- # XXX
- # XXX UNUSED CODE!
- # XXX
- # prev: make the FIFO "look" like a PrevControl...
- fp = PrevControl()
- fp.i_valid, fp._o_ready, fp.i_data = fifo.we, fifo.writable, fifo.din
- m.d.comb += fp._connect_in(self.p, True, fn=flatten)
+# 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)
- # 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),
- ]
+# 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)
+"""