bc79071046b5d358e4c9c005b9704be3c48f2ff8
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")
161 def __init__(self
, stage
):
165 if stage
is not None:
166 self
.set_specs(self
, self
)
168 def ospec(self
, name
=None):
169 assert self
._ospecfn
is not None
170 return _spec(self
._ospecfn
, name
)
172 def ispec(self
, name
=None):
173 assert self
._ispecfn
is not None
174 return _spec(self
._ispecfn
, name
)
176 def set_specs(self
, p
, n
):
177 """ sets up the ispecfn and ospecfn for getting input and output data
179 if hasattr(p
, "stage"):
181 if hasattr(n
, "stage"):
183 self
._ispecfn
= p
.ispec
184 self
._ospecfn
= n
.ospec
186 def new_specs(self
, name
):
187 """ allocates new ispec and ospec pair
189 return (_spec(self
.ispec
, "%s_i" % name
),
190 _spec(self
.ospec
, "%s_o" % name
))
192 def process(self
, i
):
193 if self
.stage
and hasattr(self
.stage
, "process"):
194 return self
.stage
.process(i
)
197 def setup(self
, m
, i
):
198 if self
.stage
is not None and hasattr(self
.stage
, "setup"):
199 self
.stage
.setup(m
, i
)
201 def _postprocess(self
, i
): # XXX DISABLED
202 return i
# RETURNS INPUT
203 if hasattr(self
.stage
, "postprocess"):
204 return self
.stage
.postprocess(i
)
208 class StageChain(StageHelper
):
209 """ pass in a list of stages (combinatorial blocks), and they will
210 automatically be chained together via their input and output specs
211 into a combinatorial chain, to create one giant combinatorial
214 the end result conforms to the exact same Stage API.
216 * input to this class will be the input of the first stage
217 * output of first stage goes into input of second
218 * output of second goes into input into third
220 * the output of this class will be the output of the last stage
222 NOTE: whilst this is very similar to ControlBase.connect(), it is
223 *really* important to appreciate that StageChain is pure
224 combinatorial and bypasses (does not involve, at all, ready/valid
225 signalling OF ANY KIND).
227 ControlBase.connect on the other hand respects, connects, and uses
228 ready/valid signalling.
232 * :chain: a chain of combinatorial blocks conforming to the Stage API
233 NOTE: StageChain.ispec and ospect have to have something
234 to return (beginning and end specs of the chain),
235 therefore the chain argument must be non-zero length
237 * :specallocate: if set, new input and output data will be allocated
238 and connected (eq'd) to each chained Stage.
239 in some cases if this is not done, the nmigen warning
240 "driving from two sources, module is being flattened"
243 NOTE: DO NOT use StageChain with combinatorial blocks that have
244 side-effects (state-based / clock-based input) or conditional
245 (inter-chain) dependencies, unless you really know what you are doing.
248 def __init__(self
, chain
, specallocate
=False):
249 assert len(chain
) > 0, "stage chain must be non-zero length"
251 StageHelper
.__init
__(self
, None)
253 self
.setup
= self
._sa
_setup
255 self
.setup
= self
._na
_setup
256 self
.set_specs(self
.chain
[0], self
.chain
[-1])
258 def _sa_setup(self
, m
, i
):
259 for (idx
, c
) in enumerate(self
.chain
):
260 if hasattr(c
, "setup"):
261 c
.setup(m
, i
) # stage may have some module stuff
262 ofn
= self
.chain
[idx
].ospec
# last assignment survives
263 cname
= 'chainin%d' % idx
264 o
= _spec(ofn
, cname
)
265 if isinstance(o
, Elaboratable
):
266 setattr(m
.submodules
, cname
, o
)
267 # process input into "o"
268 m
.d
.comb
+= nmoperator
.eq(o
, c
.process(i
))
269 if idx
== len(self
.chain
)-1:
271 ifn
= self
.chain
[idx
+1].ispec
# new input on next loop
272 i
= _spec(ifn
, 'chainin%d' % (idx
+1))
273 m
.d
.comb
+= nmoperator
.eq(i
, o
) # assign to next input
275 return self
.o
# last loop is the output
277 def _na_setup(self
, m
, i
):
278 for (idx
, c
) in enumerate(self
.chain
):
279 if hasattr(c
, "setup"):
280 c
.setup(m
, i
) # stage may have some module stuff
281 i
= o
= c
.process(i
) # store input into "o"
283 return self
.o
# last loop is the output
285 def process(self
, i
):
286 return self
.o
# conform to Stage API: return last-loop output