9217c1fee22445a44b71dd648884c9ad99eaaea4
[ieee754fpu.git] / src / add / stageapi.py
1 """ Stage API
2
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
6
7 Stage API:
8 ---------
9
10 stage requires compliance with a strict API that may be
11 implemented in several means, including as a static class.
12
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).
17
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.
24
25 the methods of a stage instance must be as follows:
26
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.
32
33 Complex to state, very simple in practice:
34 see test_buf_pipe.py for over 25 worked examples.
35
36 * ospec() - Output data format specification.
37 format requirements identical to ispec.
38
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.
47
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.
54
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
60
61 StageChain:
62 ----------
63
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.
67
68 StageHelper:
69 ----------
70
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.
74 """
75
76 from nmigen import Signal, Cat, Const, Mux, Module, Value, Elaboratable
77 from nmigen.cli import verilog, rtlil
78 from nmigen.hdl.rec import Record
79
80 from abc import ABCMeta, abstractmethod
81 from collections.abc import Sequence, Iterable
82 from collections import OrderedDict
83 import inspect
84
85 from iocontrol import PrevControl, NextControl
86 import nmoperator
87
88
89 def _spec(fn, name=None):
90 if name is None:
91 return fn()
92 varnames = dict(inspect.getmembers(fn.__code__))['co_varnames']
93 if 'name' in varnames:
94 return fn(name=name)
95 return fn()
96
97
98 class StageCls(metaclass=ABCMeta):
99 """ Class-based "Stage" API. requires instantiation (after derivation)
100
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.
105 """
106 @abstractmethod
107 def ispec(self): pass # REQUIRED
108 @abstractmethod
109 def ospec(self): pass # REQUIRED
110 #@abstractmethod
111 #def setup(self, m, i): pass # OPTIONAL
112 #@abstractmethod
113 #def process(self, i): pass # OPTIONAL
114
115
116 class Stage(metaclass=ABCMeta):
117 """ Static "Stage" API. does not require instantiation (after derivation)
118
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.
123 """
124 @staticmethod
125 @abstractmethod
126 def ispec(): pass
127
128 @staticmethod
129 @abstractmethod
130 def ospec(): pass
131
132 #@staticmethod
133 #@abstractmethod
134 #def setup(m, i): pass
135
136 #@staticmethod
137 #@abstractmethod
138 #def process(i): pass
139
140
141 class StageChain(StageCls):
142 """ pass in a list of stages, and they will automatically be
143 chained together via their input and output specs into a
144 combinatorial chain, to create one giant combinatorial block.
145
146 the end result basically conforms to the exact same Stage API.
147
148 * input to this class will be the input of the first stage
149 * output of first stage goes into input of second
150 * output of second goes into input into third
151 * ... (etc. etc.)
152 * the output of this class will be the output of the last stage
153
154 NOTE: whilst this is very similar to ControlBase.connect(), it is
155 *really* important to appreciate that StageChain is pure
156 combinatorial and bypasses (does not involve, at all, ready/valid
157 signalling of any kind).
158
159 ControlBase.connect on the other hand respects, connects, and uses
160 ready/valid signalling.
161
162 Arguments:
163
164 * :chain: a chain of combinatorial blocks conforming to the Stage API
165 NOTE: StageChain.ispec and ospect have to have something
166 to return (beginning and end specs of the chain),
167 therefore the chain argument must be non-zero length
168
169 * :specallocate: if set, new input and output data will be allocated
170 and connected (eq'd) to each chained Stage.
171 in some cases if this is not done, the nmigen warning
172 "driving from two sources, module is being flattened"
173 will be issued.
174
175 NOTE: do NOT use StageChain with combinatorial blocks that have
176 side-effects (state-based / clock-based input) or conditional
177 (inter-chain) dependencies, unless you really know what you are doing.
178 """
179 def __init__(self, chain, specallocate=False):
180 assert len(chain) > 0, "stage chain must be non-zero length"
181 self.chain = chain
182 self.specallocate = specallocate
183
184 def ispec(self):
185 """ returns the ispec of the first of the chain
186 """
187 return _spec(self.chain[0].ispec, "chainin")
188
189 def ospec(self):
190 """ returns the ospec of the last of the chain
191 """
192 return _spec(self.chain[-1].ospec, "chainout")
193
194 def _specallocate_setup(self, m, i):
195 for (idx, c) in enumerate(self.chain):
196 if hasattr(c, "setup"):
197 c.setup(m, i) # stage may have some module stuff
198 ofn = self.chain[idx].ospec # last assignment survives
199 o = _spec(ofn, 'chainin%d' % idx)
200 m.d.comb += nmoperator.eq(o, c.process(i)) # process input into "o"
201 if idx == len(self.chain)-1:
202 break
203 ifn = self.chain[idx+1].ispec # new input on next loop
204 i = _spec(ifn, 'chainin%d' % (idx+1))
205 m.d.comb += nmoperator.eq(i, o) # assign to next input
206 return o # last loop is the output
207
208 def _noallocate_setup(self, m, i):
209 for (idx, c) in enumerate(self.chain):
210 if hasattr(c, "setup"):
211 c.setup(m, i) # stage may have some module stuff
212 i = o = c.process(i) # store input into "o"
213 return o # last loop is the output
214
215 def setup(self, m, i):
216 if self.specallocate:
217 self.o = self._specallocate_setup(m, i)
218 else:
219 self.o = self._noallocate_setup(m, i)
220
221 def process(self, i):
222 return self.o # conform to Stage API: return last-loop output
223
224
225 class StageHelper(Stage):
226 """ a convenience wrapper around something that is Stage-API-compliant.
227 (that "something" may be a static class, for example).
228
229 StageHelper happens to also be compliant with the Stage API,
230 except that all the "optional" functions are provided
231 (hence the designation "convenience wrapper")
232 """
233 def __init__(self, stage):
234 self.stage = stage
235
236 def ospec(self, name):
237 assert self.stage is not None
238 return _spec(self.stage.ospec, name)
239
240 def ispec(self, name):
241 assert self.stage is not None
242 return _spec(self.stage.ispec, name)
243
244 def process(self, i):
245 if self.stage and hasattr(self.stage, "process"):
246 return self.stage.process(i)
247 return i
248
249 def setup(self, m, i):
250 if self.stage is not None and hasattr(self.stage, "setup"):
251 self.stage.setup(m, i)
252
253 def _postprocess(self, i): # XXX DISABLED
254 return i # RETURNS INPUT
255 if hasattr(self.stage, "postprocess"):
256 return self.stage.postprocess(i)
257 return i
258