the methods of a stage instance must be as follows:
- * ispec() - Input data format specification
- returns an object or a list or tuple of objects, or
- a Record, each object having an "eq" function which
- takes responsibility for copying by assignment all
- sub-objects
- * ospec() - Output data format specification
- requirements as for ospec
- * process(m, i) - Processes an ispec-formatted object
+ * ispec() - Input data format specification. Takes a bit of explaining.
+ The requirements are: something that eventually derives from
+ nmigen Value must be returned *OR* an iterator or iterable
+ or sequence (list, tuple etc.) or generator must *yield*
+ thing(s) that (eventually) derive from the nmigen Value
+ class. Complex to state, very simple in practice:
+ see test_buf_pipe.py for over 25 working examples.
+
+ * ospec() - Output data format specification.
+ requirements identical to ispec
+
+ * process(m, i) - Processes an ispec-formatted object/sequence
returns a combinatorial block of a result that
- may be assigned to the output, by way of the "eq"
- function
+ may be assigned to the output, by way of the "nmoperator.eq"
+ function. Note that what is returned here can be
+ extremely flexible. Even a dictionary can be returned
+ as long as it has fields that match precisely with the
+ Record into which its values is intended to be assigned.
+ Again: see example unit tests for details.
+
* setup(m, i) - Optional function for setting up submodules
may be used for more complex stages, to link
the input (i) to submodules. must take responsibility
Both StageCls (for use with non-static classes) and Stage (for use
by static classes) are abstract classes from which, for convenience
and as a courtesy to other developers, anything conforming to the
- Stage API may *choose* to derive.
+ Stage API may *choose* to derive. See Liskov Substitution Principle:
+ https://en.wikipedia.org/wiki/Liskov_substitution_principle
StageChain:
----------
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
raise AttributeError(e)
def __iter__(self):
- for x in self.fields.values():
+ for x in self.fields.values(): # OrderedDict so order is preserved
if isinstance(x, Iterable):
yield from x
else:
print (res)
return res
- def ports(self):
+ def ports(self): # being called "keys" would be much better
return list(self)
self.layout.fields.update(newlayout)
def __iter__(self):
- for x in self.fields.values():
+ for x in self.fields.values(): # remember: fields is an OrderedDict
if isinstance(x, Iterable):
- yield from x
+ yield from x # a bit like flatten (nmigen.tools)
else:
yield x
- def ports(self):
+ def ports(self): # would be better being called "keys"
return list(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
""" 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
class StageChain(StageCls):
""" pass in a list of stages, and they will automatically be
chained together via their input and output specs into a
- combinatorial chain.
+ combinatorial chain, to create one giant combinatorial block.
the end result basically conforms to the exact same Stage API.
* input to this class will be the input of the first stage
* output of first stage goes into input of second
- * output of second goes into input into third (etc. etc.)
+ * 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
return self.o # conform to Stage API: return last-loop output
-class ControlBase(Elaboratable):
+class StageHandler(Elaboratable):
+ """ Stage handling class
+ """
+ def __init__(self, ctrl, stage):
+ """
+ """
+ if stage is not None:
+ self.new_data(self, self, "data")
+
+ @property
+ def data_r(self):
+ return self.stage.process(self.p.data_i)
+
+ def _postprocess(self, i): # XXX DISABLED
+ return i # RETURNS INPUT
+ if hasattr(self.stage, "postprocess"):
+ return self.stage.postprocess(i)
+ return i
+
+ def new_data(self, p, n, name):
+ """ allocates new data_i and data_o
+ """
+ self.p.data_i = _spec(p.stage.ispec, "%s_i" % name)
+ self.n.data_o = _spec(n.stage.ospec, "%s_o" % name)
+
+ 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.data_i)
+
+ if not self.p.stage_ctl:
+ return m
+
+ # intercept the previous (outgoing) "ready", combine with stage 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.ready_i)
+ m.d.comb += self.n.d_valid.eq(self.n.ready_i & sdv)
+
+ return m
+
+
+class ControlBase(StageHandler):
""" Common functions for Pipeline API. Note: a "pipeline stage" only
exists (conceptually) when a ControlBase derivative is handed
a Stage (combinatorial block)
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.data_i = _spec(stage.ispec, "data_i") # input type
- self.n.data_o = _spec(stage.ospec, "data_o") # output type
+ StageHandler.__init__(self, self, stage)
def connect_to_next(self, nxt):
""" helper function to connect to the next stage data/valid/ready.
pipe2 = pipechain[i+1]
eqs += pipe1.connect_to_next(pipe2)
- # connect front of chain to ourselves
+ # connect front and back of chain to ourselves
front = pipechain[0]
- self.p.data_i = _spec(front.stage.ispec, "chainin")
- eqs += front._connect_in(self)
-
- # connect end of chain to ourselves
end = pipechain[-1]
- self.n.data_o = _spec(end.stage.ospec, "chainout")
+ self.new_data(front, end, "chain") # NOTE: REPLACES existing data
+ eqs += front._connect_in(self)
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
"""
def ports(self):
return list(self)
- 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.data_i)
-
- if not self.p.stage_ctl:
- return m
-
- # intercept the previous (outgoing) "ready", combine with stage 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.ready_i)
- m.d.comb += self.n.d_valid.eq(self.n.ready_i & sdv)
-
- return m
-