3 Associated development bugs:
4 * http://bugs.libre-riscv.org/show_bug.cgi?id=64
5 * http://bugs.libre-riscv.org/show_bug.cgi?id=57
10 stage requires compliance with a strict API that may be
11 implemented in several means, including as a static class.
13 Stages do not HOLD data, and they definitely do not contain
14 signalling (ready/valid). They do however specify the FORMAT
15 of the incoming and outgoing data, and they provide a means to
16 PROCESS that data (from incoming format to outgoing format).
18 Stage Blocks really must be combinatorial blocks. It would be ok
19 to have input come in from sync'd sources (clock-driven) however by
20 doing so they would no longer be deterministic, and chaining such
21 blocks with such side-effects together could result in unexpected,
22 unpredictable, unreproduceable behaviour.
23 So generally to be avoided, then unless you know what you are doing.
25 the methods of a stage instance must be as follows:
27 * ispec() - Input data format specification. Takes a bit of explaining.
28 The requirements are: something that eventually derives from
29 nmigen Value must be returned *OR* an iterator or iterable
30 or sequence (list, tuple etc.) or generator must *yield*
31 thing(s) that (eventually) derive from the nmigen Value class.
33 Complex to state, very simple in practice:
34 see test_buf_pipe.py for over 25 worked examples.
36 * ospec() - Output data format specification.
37 format requirements identical to ispec.
39 * process(m, i) - Optional function for processing ispec-formatted data.
40 returns a combinatorial block of a result that
41 may be assigned to the output, by way of the "nmoperator.eq"
42 function. Note that what is returned here can be
43 extremely flexible. Even a dictionary can be returned
44 as long as it has fields that match precisely with the
45 Record into which its values is intended to be assigned.
46 Again: see example unit tests for details.
48 * setup(m, i) - Optional function for setting up submodules.
49 may be used for more complex stages, to link
50 the input (i) to submodules. must take responsibility
51 for adding those submodules to the module (m).
52 the submodules must be combinatorial blocks and
53 must have their inputs and output linked combinatorially.
55 Both StageCls (for use with non-static classes) and Stage (for use
56 by static classes) are abstract classes from which, for convenience
57 and as a courtesy to other developers, anything conforming to the
58 Stage API may *choose* to derive. See Liskov Substitution Principle:
59 https://en.wikipedia.org/wiki/Liskov_substitution_principle
64 A useful combinatorial wrapper around stages that chains them together
65 and then presents a Stage-API-conformant interface. By presenting
66 the same API as the stages it wraps, it can clearly be used recursively.
71 A convenience wrapper around a Stage-API-compliant "thing" which
72 complies with the Stage API and provides mandatory versions of
73 all the optional bits.
76 from abc
import ABCMeta
, abstractmethod
82 def _spec(fn
, name
=None):
83 """ useful function that determines if "fn" has an argument "name".
84 if so, fn(name) is called otherwise fn() is called.
86 means that ispec and ospec can be declared with *or without*
87 a name argument. normally it would be necessary to have
88 "ispec(name=None)" to achieve the same effect.
92 varnames
= dict(inspect
.getmembers(fn
.__code
__))['co_varnames']
93 if 'name' in varnames
:
98 class StageCls(metaclass
=ABCMeta
):
99 """ Class-based "Stage" API. requires instantiation (after derivation)
101 see "Stage API" above.. Note: python does *not* require derivation
102 from this class. All that is required is that the pipelines *have*
103 the functions listed in this class. Derivation from this class
104 is therefore merely a "courtesy" to maintainers.
107 def ispec(self
): pass # REQUIRED
109 def ospec(self
): pass # REQUIRED
111 #def setup(self, m, i): pass # OPTIONAL
113 #def process(self, i): pass # OPTIONAL
116 class Stage(metaclass
=ABCMeta
):
117 """ Static "Stage" API. does not require instantiation (after derivation)
119 see "Stage API" above. Note: python does *not* require derivation
120 from this class. All that is required is that the pipelines *have*
121 the functions listed in this class. Derivation from this class
122 is therefore merely a "courtesy" to maintainers.
134 #def setup(m, i): pass
138 #def process(i): pass
141 class StageHelper(Stage
):
142 """ a convenience wrapper around something that is Stage-API-compliant.
143 (that "something" may be a static class, for example).
145 StageHelper happens to also be compliant with the Stage API,
146 it differs from the stage that it wraps in that all the "optional"
147 functions are provided (hence the designation "convenience wrapper")
149 def __init__(self
, stage
):
153 if stage
is not None:
154 self
.set_specs(self
, self
)
156 def ospec(self
, name
):
157 assert self
._ospecfn
is not None
158 return _spec(self
._ospecfn
, name
)
160 def ispec(self
, name
):
161 assert self
._ispecfn
is not None
162 return _spec(self
._ispecfn
, name
)
164 def set_specs(self
, p
, n
):
165 """ sets up the ispecfn and ospecfn for getting input and output data
167 if hasattr(p
, "stage"):
169 if hasattr(n
, "stage"):
171 self
._ispecfn
= p
.ispec
172 self
._ospecfn
= n
.ospec
174 def new_specs(self
, name
):
175 """ allocates new ispec and ospec pair
177 return self
.ispec("%s_i" % name
), self
.ospec("%s_o" % name
)
179 def process(self
, i
):
180 if self
.stage
and hasattr(self
.stage
, "process"):
181 return self
.stage
.process(i
)
184 def setup(self
, m
, i
):
185 if self
.stage
is not None and hasattr(self
.stage
, "setup"):
186 self
.stage
.setup(m
, i
)
188 def _postprocess(self
, i
): # XXX DISABLED
189 return i
# RETURNS INPUT
190 if hasattr(self
.stage
, "postprocess"):
191 return self
.stage
.postprocess(i
)
195 class StageChain(StageHelper
):
196 """ pass in a list of stages, and they will automatically be
197 chained together via their input and output specs into a
198 combinatorial chain, to create one giant combinatorial block.
200 the end result basically conforms to the exact same Stage API.
202 * input to this class will be the input of the first stage
203 * output of first stage goes into input of second
204 * output of second goes into input into third
206 * the output of this class will be the output of the last stage
208 NOTE: whilst this is very similar to ControlBase.connect(), it is
209 *really* important to appreciate that StageChain is pure
210 combinatorial and bypasses (does not involve, at all, ready/valid
211 signalling of any kind).
213 ControlBase.connect on the other hand respects, connects, and uses
214 ready/valid signalling.
218 * :chain: a chain of combinatorial blocks conforming to the Stage API
219 NOTE: StageChain.ispec and ospect have to have something
220 to return (beginning and end specs of the chain),
221 therefore the chain argument must be non-zero length
223 * :specallocate: if set, new input and output data will be allocated
224 and connected (eq'd) to each chained Stage.
225 in some cases if this is not done, the nmigen warning
226 "driving from two sources, module is being flattened"
229 NOTE: do NOT use StageChain with combinatorial blocks that have
230 side-effects (state-based / clock-based input) or conditional
231 (inter-chain) dependencies, unless you really know what you are doing.
233 def __init__(self
, chain
, specallocate
=False):
234 assert len(chain
) > 0, "stage chain must be non-zero length"
236 StageHelper
.__init
__(self
, None)
237 self
.setup
= self
._sa
_setup
if specallocate
else self
._na
_setup
238 self
.set_specs(self
.chain
[0], self
.chain
[-1])
240 def _sa_setup(self
, m
, i
):
241 for (idx
, c
) in enumerate(self
.chain
):
242 if hasattr(c
, "setup"):
243 c
.setup(m
, i
) # stage may have some module stuff
244 ofn
= self
.chain
[idx
].ospec
# last assignment survives
245 o
= _spec(ofn
, 'chainin%d' % idx
)
246 m
.d
.comb
+= nmoperator
.eq(o
, c
.process(i
)) # process input into "o"
247 if idx
== len(self
.chain
)-1:
249 ifn
= self
.chain
[idx
+1].ispec
# new input on next loop
250 i
= _spec(ifn
, 'chainin%d' % (idx
+1))
251 m
.d
.comb
+= nmoperator
.eq(i
, o
) # assign to next input
253 return self
.o
# last loop is the output
255 def _na_setup(self
, m
, i
):
256 for (idx
, c
) in enumerate(self
.chain
):
257 if hasattr(c
, "setup"):
258 c
.setup(m
, i
) # stage may have some module stuff
259 i
= o
= c
.process(i
) # store input into "o"
261 return self
.o
# last loop is the output
263 def process(self
, i
):
264 return self
.o
# conform to Stage API: return last-loop output