stage requires compliance with a strict API that may be
implemented in several means, including as a static class.
+
+ Stage Blocks really must be combinatorial blocks. It would be ok
+ to have input come in from sync'd sources (clock-driven) however by
+ doing so they would no longer be deterministic, and chaining such
+ blocks with such side-effects together could result in unexpected,
+ unpredictable, unreproduceable behaviour.
+ So generally to be avoided, then unless you know what you are doing.
+
the methods of a stage instance must be as follows:
* ispec() - Input data format specification
Also has an extremely useful "connect" function that can be used to
connect a chain of pipelines and present the exact same prev/next
ready/valid/data API.
+
+ Note: pipelines basically do not become pipelines as such until
+ handed to a derivative of ControlBase. ControlBase itself is *not*
+ strictly considered a pipeline class. Wishbone and AXI4 (master or
+ slave) could be derived from ControlBase, for example.
"""
from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
from collections import OrderedDict
import inspect
-from nmoperator import eq, cat, shape
+import nmoperator
class Object:
elif isinstance(v, Value):
newlayout = {k: (k, v.shape())}
else:
- newlayout = {k: (k, shape(v))}
+ newlayout = {k: (k, nmoperator.shape(v))}
self.layout.fields.update(newlayout)
def __iter__(self):
may be a multi-bit signal, where all bits are required
to be asserted to indicate "valid".
* ready_o: output to next stage indicating readiness to accept data
- * data_i : an input - added by the user of this class
+ * data_i : an input - MUST be added by the USER of this class
"""
def __init__(self, i_width=1, stage_ctl=False):
self.stage_ctl = stage_ctl
self.valid_i = Signal(i_width, name="p_valid_i") # prev >>in self
- self._ready_o = Signal(name="p_ready_o") # prev <<out self
+ self._ready_o = Signal(name="p_ready_o") # prev <<out self
self.data_i = None # XXX MUST BE ADDED BY USER
if stage_ctl:
- self.s_ready_o = 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
data_i = fn(prev.data_i) if fn is not None else prev.data_i
return [self.valid_i.eq(valid_i),
prev.ready_o.eq(self.ready_o),
- eq(self.data_i, data_i),
+ nmoperator.eq(self.data_i, data_i),
]
@property
""" contains the signals that go *to* the next stage (both in and out)
* valid_o: output indicating to next stage that data is valid
* ready_i: input from next stage indicating that it can accept data
- * data_o : an output - added by the user of this class
+ * data_o : an output - MUST be added by the USER of this class
"""
def __init__(self, stage_ctl=False):
self.stage_ctl = stage_ctl
"""
return [nxt.valid_i.eq(self.valid_o),
self.ready_i.eq(nxt.ready_o),
- eq(nxt.data_i, self.data_o),
+ nmoperator.eq(nxt.data_i, self.data_o),
]
def _connect_out(self, nxt, direct=False, fn=None):
data_o = fn(nxt.data_o) if fn is not None else nxt.data_o
return [nxt.valid_o.eq(self.valid_o),
self.ready_i.eq(ready_i),
- eq(data_o, self.data_o),
+ nmoperator.eq(data_o, self.data_o),
]
def elaborate(self, platform):
* output of second goes into input into third (etc. etc.)
* the output of this class will be the output of the last stage
+ NOTE: whilst this is very similar to ControlBase.connect(), it is
+ *really* important to appreciate that StageChain is pure
+ combinatorial and bypasses (does not involve, at all, ready/valid
+ signalling of any kind).
+
+ ControlBase.connect on the other hand respects, connects, and uses
+ ready/valid signalling.
+
Arguments:
* :chain: a chain of combinatorial blocks conforming to the Stage API
- NOTE: this may be an EMPTY list (or tuple, or iterable).
+ NOTE: StageChain.ispec and ospect have to have something
+ to return (beginning and end specs of the chain),
+ therefore the chain argument must be non-zero length
+
* :specallocate: if set, new input and output data will be allocated
and connected (eq'd) to each chained Stage.
in some cases if this is not done, the nmigen warning
(inter-chain) dependencies, unless you really know what you are doing.
"""
def __init__(self, chain, specallocate=False):
+ assert len(chain) > 0, "stage chain must be non-zero length"
self.chain = chain
self.specallocate = specallocate
def ispec(self):
+ """ returns the ispec of the first of the chain
+ """
return _spec(self.chain[0].ispec, "chainin")
def ospec(self):
+ """ returns the ospec of the last of the chain
+ """
return _spec(self.chain[-1].ospec, "chainout")
def _specallocate_setup(self, m, i):
c.setup(m, i) # stage may have some module stuff
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"
+ m.d.comb += nmoperator.eq(o, c.process(i)) # process input into "o"
if idx == len(self.chain)-1:
break
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
+ m.d.comb += nmoperator.eq(i, o) # assign to next input
return o # last loop is the output
def _noallocate_setup(self, m, i):
class ControlBase(Elaboratable):
- """ Common functions for Pipeline API
+ """ Common functions for Pipeline API. Note: a "pipeline stage" only
+ exists (conceptually) when a ControlBase derivative is handed
+ a Stage (combinatorial block)
"""
def __init__(self, stage=None, in_multi=None, stage_ctl=False):
""" Base class containing ready/valid/data to previous and next stages
Thus it becomes possible to build up larger chains recursively.
More complex chains (multi-input, multi-output) will have to be
done manually.
+
+ Argument:
+
+ * :pipechain: - a sequence of ControlBase-derived classes
+ (must be one or more in length)
+
+ Returns:
+
+ * a list of eq assignments that will need to be added in
+ an elaborate() to m.d.comb
"""
+ assert len(pipechain) > 0, "pipechain must be non-zero length"
eqs = [] # collated list of assignment statements
# connect inter-chain
def set_input(self, i):
""" helper function to set the input data
"""
- return eq(self.p.data_i, i)
+ return nmoperator.eq(self.p.data_i, i)
def __iter__(self):
yield from self.p
return m
-