3 This work is funded through NLnet under Grant 2019-02-012
8 Associated development bugs:
9 * http://bugs.libre-riscv.org/show_bug.cgi?id=148
10 * http://bugs.libre-riscv.org/show_bug.cgi?id=64
11 * http://bugs.libre-riscv.org/show_bug.cgi?id=57
16 stage requires compliance with a strict API that may be
17 implemented in several means, including as a static class.
19 Stages do not HOLD data, and they definitely do not contain
20 signalling (ready/valid). They do however specify the FORMAT
21 of the incoming and outgoing data, and they provide a means to
22 PROCESS that data (from incoming format to outgoing format).
24 Stage Blocks really should be combinatorial blocks (Moore FSMs).
25 It would be ok to have input come in from sync'd sources
26 (clock-driven, Mealy FSMs) however by doing so they would no longer
27 be deterministic, and chaining such blocks with such side-effects
28 together could result in unexpected, unpredictable, unreproduceable
31 So generally to be avoided, then unless you know what you are doing.
32 https://en.wikipedia.org/wiki/Moore_machine
33 https://en.wikipedia.org/wiki/Mealy_machine
35 the methods of a stage instance must be as follows:
37 * ispec() - Input data format specification. Takes a bit of explaining.
38 The requirements are: something that eventually derives from
39 nmigen Value must be returned *OR* an iterator or iterable
40 or sequence (list, tuple etc.) or generator must *yield*
41 thing(s) that (eventually) derive from the nmigen Value class.
43 Complex to state, very simple in practice:
44 see test_buf_pipe.py for over 25 worked examples.
46 * ospec() - Output data format specification.
47 format requirements identical to ispec.
49 * process(m, i) - Optional function for processing ispec-formatted data.
50 returns a combinatorial block of a result that
51 may be assigned to the output, by way of the "nmoperator.eq"
52 function. Note that what is returned here can be
53 extremely flexible. Even a dictionary can be returned
54 as long as it has fields that match precisely with the
55 Record into which its values is intended to be assigned.
56 Again: see example unit tests for details.
58 * setup(m, i) - Optional function for setting up submodules.
59 may be used for more complex stages, to link
60 the input (i) to submodules. must take responsibility
61 for adding those submodules to the module (m).
62 the submodules must be combinatorial blocks and
63 must have their inputs and output linked combinatorially.
65 Both StageCls (for use with non-static classes) and Stage (for use
66 by static classes) are abstract classes from which, for convenience
67 and as a courtesy to other developers, anything conforming to the
68 Stage API may *choose* to derive. See Liskov Substitution Principle:
69 https://en.wikipedia.org/wiki/Liskov_substitution_principle
74 A useful combinatorial wrapper around stages that chains them together
75 and then presents a Stage-API-conformant interface. By presenting
76 the same API as the stages it wraps, it can clearly be used recursively.
81 A convenience wrapper around a Stage-API-compliant "thing" which
82 complies with the Stage API and provides mandatory versions of
83 all the optional bits.
86 from nmigen
import Elaboratable
87 from abc
import ABCMeta
, abstractmethod
90 from nmutil
import nmoperator
93 def _spec(fn
, name
=None):
94 """ useful function that determines if "fn" has an argument "name".
95 if so, fn(name) is called otherwise fn() is called.
97 means that ispec and ospec can be declared with *or without*
98 a name argument. normally it would be necessary to have
99 "ispec(name=None)" to achieve the same effect.
103 varnames
= dict(inspect
.getmembers(fn
.__code
__))['co_varnames']
104 if 'name' in varnames
:
109 class StageCls(metaclass
=ABCMeta
):
110 """ Class-based "Stage" API. requires instantiation (after derivation)
112 see "Stage API" above.. Note: python does *not* require derivation
113 from this class. All that is required is that the pipelines *have*
114 the functions listed in this class. Derivation from this class
115 is therefore merely a "courtesy" to maintainers.
118 def ispec(self
): pass # REQUIRED
120 def ospec(self
): pass # REQUIRED
122 #def setup(self, m, i): pass # OPTIONAL
124 #def process(self, i): pass # OPTIONAL
127 class Stage(metaclass
=ABCMeta
):
128 """ Static "Stage" API. does not require instantiation (after derivation)
130 see "Stage API" above. Note: python does *not* require derivation
131 from this class. All that is required is that the pipelines *have*
132 the functions listed in this class. Derivation from this class
133 is therefore merely a "courtesy" to maintainers.
145 #def setup(m, i): pass
149 #def process(i): pass
152 class StageHelper(Stage
):
153 """ a convenience wrapper around something that is Stage-API-compliant.
154 (that "something" may be a static class, for example).
156 StageHelper happens to also be compliant with the Stage API,
157 it differs from the stage that it wraps in that all the "optional"
158 functions are provided (hence the designation "convenience wrapper")
160 def __init__(self
, stage
):
164 if stage
is not None:
165 self
.set_specs(self
, self
)
167 def ospec(self
, name
=None):
168 assert self
._ospecfn
is not None
169 return _spec(self
._ospecfn
, name
)
171 def ispec(self
, name
=None):
172 assert self
._ispecfn
is not None
173 return _spec(self
._ispecfn
, name
)
175 def set_specs(self
, p
, n
):
176 """ sets up the ispecfn and ospecfn for getting input and output data
178 if hasattr(p
, "stage"):
180 if hasattr(n
, "stage"):
182 self
._ispecfn
= p
.ispec
183 self
._ospecfn
= n
.ospec
185 def new_specs(self
, name
):
186 """ allocates new ispec and ospec pair
188 return (_spec(self
.ispec
, "%s_i" % name
),
189 _spec(self
.ospec
, "%s_o" % name
))
191 def process(self
, i
):
192 if self
.stage
and hasattr(self
.stage
, "process"):
193 return self
.stage
.process(i
)
196 def setup(self
, m
, i
):
197 if self
.stage
is not None and hasattr(self
.stage
, "setup"):
198 self
.stage
.setup(m
, i
)
200 def _postprocess(self
, i
): # XXX DISABLED
201 return i
# RETURNS INPUT
202 if hasattr(self
.stage
, "postprocess"):
203 return self
.stage
.postprocess(i
)
207 class StageChain(StageHelper
):
208 """ pass in a list of stages (combinatorial blocks), and they will
209 automatically be chained together via their input and output specs
210 into a combinatorial chain, to create one giant combinatorial
213 the end result conforms to the exact same Stage API.
215 * input to this class will be the input of the first stage
216 * output of first stage goes into input of second
217 * output of second goes into input into third
219 * the output of this class will be the output of the last stage
221 NOTE: whilst this is very similar to ControlBase.connect(), it is
222 *really* important to appreciate that StageChain is pure
223 combinatorial and bypasses (does not involve, at all, ready/valid
224 signalling OF ANY KIND).
226 ControlBase.connect on the other hand respects, connects, and uses
227 ready/valid signalling.
231 * :chain: a chain of combinatorial blocks conforming to the Stage API
232 NOTE: StageChain.ispec and ospect have to have something
233 to return (beginning and end specs of the chain),
234 therefore the chain argument must be non-zero length
236 * :specallocate: if set, new input and output data will be allocated
237 and connected (eq'd) to each chained Stage.
238 in some cases if this is not done, the nmigen warning
239 "driving from two sources, module is being flattened"
242 NOTE: DO NOT use StageChain with combinatorial blocks that have
243 side-effects (state-based / clock-based input) or conditional
244 (inter-chain) dependencies, unless you really know what you are doing.
246 def __init__(self
, chain
, specallocate
=False):
247 assert len(chain
) > 0, "stage chain must be non-zero length"
249 StageHelper
.__init
__(self
, None)
251 self
.setup
= self
._sa
_setup
253 self
.setup
= self
._na
_setup
254 self
.set_specs(self
.chain
[0], self
.chain
[-1])
256 def _sa_setup(self
, m
, i
):
257 for (idx
, c
) in enumerate(self
.chain
):
258 if hasattr(c
, "setup"):
259 c
.setup(m
, i
) # stage may have some module stuff
260 ofn
= self
.chain
[idx
].ospec
# last assignment survives
261 cname
= 'chainin%d' % idx
262 o
= _spec(ofn
, cname
)
263 if isinstance(o
, Elaboratable
):
264 setattr(m
.submodules
, cname
, o
)
265 m
.d
.comb
+= nmoperator
.eq(o
, c
.process(i
)) # process input into "o"
266 if idx
== len(self
.chain
)-1:
268 ifn
= self
.chain
[idx
+1].ispec
# new input on next loop
269 i
= _spec(ifn
, 'chainin%d' % (idx
+1))
270 m
.d
.comb
+= nmoperator
.eq(i
, o
) # assign to next input
272 return self
.o
# last loop is the output
274 def _na_setup(self
, m
, i
):
275 for (idx
, c
) in enumerate(self
.chain
):
276 if hasattr(c
, "setup"):
277 c
.setup(m
, i
) # stage may have some module stuff
278 i
= o
= c
.process(i
) # store input into "o"
280 return self
.o
# last loop is the output
282 def process(self
, i
):
283 return self
.o
# conform to Stage API: return last-loop output