move singlepipe, multipipe, nmoperator and pipeline.py to nmutil
[ieee754fpu.git] / src / nmutil / pipeline.py
1 """ Example 5: Making use of PyRTL and Introspection. """
2
3 from collections.abc import Sequence
4
5 from nmigen import Signal
6 from nmigen.hdl.rec import Record
7 from nmigen import tracer
8 from nmigen.compat.fhdl.bitcontainer import value_bits_sign
9 from contextlib import contextmanager
10
11 from nmoperator import eq
12 from singlepipe import StageCls, ControlBase, BufferedHandshake
13 from singlepipe import UnbufferedPipeline
14
15
16 # The following example shows how pyrtl can be used to make some interesting
17 # hardware structures using python introspection. In particular, this example
18 # makes a N-stage pipeline structure. Any specific pipeline is then a derived
19 # class of SimplePipeline where methods with names starting with "stage" are
20 # stages, and new members with names not starting with "_" are to be registered
21 # for the next stage.
22
23 def like(value, rname, pipe, pipemode=False):
24 if isinstance(value, ObjectProxy):
25 return ObjectProxy.like(pipe, value, pipemode=pipemode,
26 name=rname, reset_less=True)
27 else:
28 return Signal(value_bits_sign(value), name=rname,
29 reset_less=True)
30 return Signal.like(value, name=rname, reset_less=True)
31
32 def get_assigns(_assigns):
33 assigns = []
34 for e in _assigns:
35 if isinstance(e, ObjectProxy):
36 assigns += get_assigns(e._assigns)
37 else:
38 assigns.append(e)
39 return assigns
40
41
42 def get_eqs(_eqs):
43 eqs = []
44 for e in _eqs:
45 if isinstance(e, ObjectProxy):
46 eqs += get_eqs(e._eqs)
47 else:
48 eqs.append(e)
49 return eqs
50
51
52 class ObjectProxy:
53 def __init__(self, m, name=None, pipemode=False, syncmode=True):
54 self._m = m
55 if name is None:
56 name = tracer.get_var_name(default=None)
57 self.name = name
58 self._pipemode = pipemode
59 self._syncmode = syncmode
60 self._eqs = {}
61 self._assigns = []
62 self._preg_map = {}
63
64 @classmethod
65 def like(cls, m, value, pipemode=False, name=None, src_loc_at=0, **kwargs):
66 name = name or tracer.get_var_name(depth=2 + src_loc_at,
67 default="$like")
68
69 src_loc_at_1 = 1 + src_loc_at
70 r = ObjectProxy(m, value.name, pipemode)
71 #for a, aname in value._preg_map.items():
72 # r._preg_map[aname] = like(a, aname, m, pipemode)
73 for a in value.ports():
74 aname = a.name
75 r._preg_map[aname] = like(a, aname, m, pipemode)
76 return r
77
78 def __repr__(self):
79 subobjs = []
80 for a in self.ports():
81 aname = a.name
82 ai = self._preg_map[aname]
83 subobjs.append(repr(ai))
84 return "<OP %s>" % subobjs
85
86 def get_specs(self, liked=False):
87 res = []
88 for k, v in self._preg_map.items():
89 #v = like(v, k, stage._m)
90 res.append(v)
91 if isinstance(v, ObjectProxy):
92 res += v.get_specs()
93 return res
94
95 def eq(self, i):
96 print ("ObjectProxy eq", self, i)
97 res = []
98 for a in self.ports():
99 aname = a.name
100 ai = i._preg_map[aname]
101 res.append(a.eq(ai))
102 return res
103
104 def ports(self):
105 res = []
106 for aname, a in self._preg_map.items():
107 if isinstance(a, Signal) or isinstance(a, ObjectProxy) or \
108 isinstance(a, Record):
109 res.append(a)
110 #print ("ObjectPorts", res)
111 return res
112
113 def __getattr__(self, name):
114 try:
115 v = self._preg_map[name]
116 return v
117 #return like(v, name, self._m)
118 except KeyError:
119 raise AttributeError(
120 'error, no pipeline register "%s" defined for OP %s'
121 % (name, self.name))
122
123 def __setattr__(self, name, value):
124 if name.startswith('_') or name in ['name', 'ports', 'eq', 'like']:
125 # do not do anything tricky with variables starting with '_'
126 object.__setattr__(self, name, value)
127 return
128 #rname = "%s_%s" % (self.name, name)
129 rname = name
130 new_pipereg = like(value, rname, self._m, self._pipemode)
131 self._preg_map[name] = new_pipereg
132 #object.__setattr__(self, name, new_pipereg)
133 if self._pipemode:
134 #print ("OP pipemode", self._syncmode, new_pipereg, value)
135 assign = eq(new_pipereg, value)
136 if self._syncmode:
137 self._m.d.sync += assign
138 else:
139 self._m.d.comb += assign
140 elif self._m:
141 #print ("OP !pipemode assign", new_pipereg, value, type(value))
142 self._m.d.comb += eq(new_pipereg, value)
143 else:
144 #print ("OP !pipemode !m", new_pipereg, value, type(value))
145 self._assigns += eq(new_pipereg, value)
146 if isinstance(value, ObjectProxy):
147 #print ("OP, defer assigns:", value._assigns)
148 self._assigns += value._assigns
149 self._eqs.append(value._eqs)
150
151
152 class PipelineStage:
153 """ Pipeline builder stage with auto generation of pipeline registers.
154 """
155
156 def __init__(self, name, m, prev=None, pipemode=False, ispec=None):
157 self._m = m
158 self._stagename = name
159 self._preg_map = {'__nextstage__': {}}
160 self._prev_stage = prev
161 self._ispec = ispec
162 if ispec:
163 self._preg_map[self._stagename] = ispec
164 if prev:
165 print ("prev", prev._stagename, prev._preg_map)
166 #if prev._stagename in prev._preg_map:
167 # m = prev._preg_map[prev._stagename]
168 # self._preg_map[prev._stagename] = m
169 if '__nextstage__' in prev._preg_map:
170 m = prev._preg_map['__nextstage__']
171 m = likedict(m)
172 self._preg_map[self._stagename] = m
173 #for k, v in m.items():
174 #m[k] = like(v, k, self._m)
175 print ("make current", self._stagename, m)
176 self._pipemode = pipemode
177 self._eqs = {}
178 self._assigns = []
179
180 def __getattribute__(self, name):
181 if name.startswith('_'):
182 return object.__getattribute__(self, name)
183 #if name in self._preg_map['__nextstage__']:
184 # return self._preg_map['__nextstage__'][name]
185 try:
186 print ("getattr", name, object.__getattribute__(self, '_preg_map'))
187 v = self._preg_map[self._stagename][name]
188 return v
189 #return like(v, name, self._m)
190 except KeyError:
191 raise AttributeError(
192 'error, no pipeline register "%s" defined for stage %s'
193 % (name, self._stagename))
194
195 def __setattr__(self, name, value):
196 if name.startswith('_'):
197 # do not do anything tricky with variables starting with '_'
198 object.__setattr__(self, name, value)
199 return
200 pipereg_id = self._stagename
201 rname = 'pipereg_' + pipereg_id + '_' + name
202 new_pipereg = like(value, rname, self._m, self._pipemode)
203 next_stage = '__nextstage__'
204 if next_stage not in self._preg_map:
205 self._preg_map[next_stage] = {}
206 self._preg_map[next_stage][name] = new_pipereg
207 print ("setattr", name, value, self._preg_map)
208 if self._pipemode:
209 self._eqs[name] = new_pipereg
210 assign = eq(new_pipereg, value)
211 print ("pipemode: append", new_pipereg, value, assign)
212 if isinstance(value, ObjectProxy):
213 print ("OP, assigns:", value._assigns)
214 self._assigns += value._assigns
215 self._eqs[name]._eqs = value._eqs
216 #self._m.d.comb += assign
217 self._assigns += assign
218 elif self._m:
219 print ("!pipemode: assign", new_pipereg, value)
220 assign = eq(new_pipereg, value)
221 self._m.d.sync += assign
222 else:
223 print ("!pipemode !m: defer assign", new_pipereg, value)
224 assign = eq(new_pipereg, value)
225 self._eqs[name] = new_pipereg
226 self._assigns += assign
227 if isinstance(value, ObjectProxy):
228 print ("OP, defer assigns:", value._assigns)
229 self._assigns += value._assigns
230 self._eqs[name]._eqs = value._eqs
231
232 def likelist(specs):
233 res = []
234 for v in specs:
235 res.append(like(v, v.name, None, pipemode=True))
236 return res
237
238 def likedict(specs):
239 if not isinstance(specs, dict):
240 return like(specs, specs.name, None, pipemode=True)
241 res = {}
242 for k, v in specs.items():
243 res[k] = likedict(v)
244 return res
245
246
247 class AutoStage(StageCls):
248 def __init__(self, inspecs, outspecs, eqs, assigns):
249 self.inspecs, self.outspecs = inspecs, outspecs
250 self.eqs, self.assigns = eqs, assigns
251 #self.o = self.ospec()
252 def ispec(self): return likedict(self.inspecs)
253 def ospec(self): return likedict(self.outspecs)
254
255 def process(self, i):
256 print ("stage process", i)
257 return self.eqs
258
259 def setup(self, m, i):
260 print ("stage setup i", i, m)
261 print ("stage setup inspecs", self.inspecs)
262 print ("stage setup outspecs", self.outspecs)
263 print ("stage setup eqs", self.eqs)
264 #self.o = self.ospec()
265 m.d.comb += eq(self.inspecs, i)
266 #m.d.comb += eq(self.outspecs, self.eqs)
267 #m.d.comb += eq(self.o, i)
268
269
270 class AutoPipe(UnbufferedPipeline):
271 def __init__(self, stage, assigns):
272 UnbufferedPipeline.__init__(self, stage)
273 self.assigns = assigns
274
275 def elaborate(self, platform):
276 m = UnbufferedPipeline.elaborate(self, platform)
277 m.d.comb += self.assigns
278 print ("assigns", self.assigns, m)
279 return m
280
281
282 class PipeManager:
283 def __init__(self, m, pipemode=False, pipetype=None):
284 self.m = m
285 self.pipemode = pipemode
286 self.pipetype = pipetype
287
288 @contextmanager
289 def Stage(self, name, prev=None, ispec=None):
290 if ispec:
291 ispec = likedict(ispec)
292 print ("start stage", name, ispec)
293 stage = PipelineStage(name, None, prev, self.pipemode, ispec=ispec)
294 try:
295 yield stage, self.m #stage._m
296 finally:
297 pass
298 if self.pipemode:
299 if stage._ispec:
300 print ("use ispec", stage._ispec)
301 inspecs = stage._ispec
302 else:
303 inspecs = self.get_specs(stage, name)
304 #inspecs = likedict(inspecs)
305 outspecs = self.get_specs(stage, '__nextstage__', liked=True)
306 print ("stage inspecs", name, inspecs)
307 print ("stage outspecs", name, outspecs)
308 eqs = stage._eqs # get_eqs(stage._eqs)
309 assigns = get_assigns(stage._assigns)
310 print ("stage eqs", name, eqs)
311 print ("stage assigns", name, assigns)
312 s = AutoStage(inspecs, outspecs, eqs, assigns)
313 self.stages.append(s)
314 print ("end stage", name, self.pipemode, "\n")
315
316 def get_specs(self, stage, name, liked=False):
317 return stage._preg_map[name]
318 if name in stage._preg_map:
319 res = []
320 for k, v in stage._preg_map[name].items():
321 #v = like(v, k, stage._m)
322 res.append(v)
323 #if isinstance(v, ObjectProxy):
324 # res += v.get_specs()
325 return res
326 return {}
327
328 def __enter__(self):
329 self.stages = []
330 return self
331
332 def __exit__(self, *args):
333 print ("exit stage", args)
334 pipes = []
335 cb = ControlBase()
336 for s in self.stages:
337 print ("stage specs", s, s.inspecs, s.outspecs)
338 if self.pipetype == 'buffered':
339 p = BufferedHandshake(s)
340 else:
341 p = AutoPipe(s, s.assigns)
342 pipes.append(p)
343 self.m.submodules += p
344
345 self.m.d.comb += cb.connect(pipes)
346
347
348 class SimplePipeline:
349 """ Pipeline builder with auto generation of pipeline registers.
350 """
351
352 def __init__(self, m):
353 self._m = m
354 self._pipeline_register_map = {}
355 self._current_stage_num = 0
356
357 def _setup(self):
358 stage_list = []
359 for method in dir(self):
360 if method.startswith('stage'):
361 stage_list.append(method)
362 for stage in sorted(stage_list):
363 stage_method = getattr(self, stage)
364 stage_method()
365 self._current_stage_num += 1
366
367 def __getattr__(self, name):
368 try:
369 return self._pipeline_register_map[self._current_stage_num][name]
370 except KeyError:
371 raise AttributeError(
372 'error, no pipeline register "%s" defined for stage %d'
373 % (name, self._current_stage_num))
374
375 def __setattr__(self, name, value):
376 if name.startswith('_'):
377 # do not do anything tricky with variables starting with '_'
378 object.__setattr__(self, name, value)
379 return
380 next_stage = self._current_stage_num + 1
381 pipereg_id = str(self._current_stage_num) + 'to' + str(next_stage)
382 rname = 'pipereg_' + pipereg_id + '_' + name
383 #new_pipereg = Signal(value_bits_sign(value), name=rname,
384 # reset_less=True)
385 if isinstance(value, ObjectProxy):
386 new_pipereg = ObjectProxy.like(self._m, value,
387 name=rname, reset_less = True)
388 else:
389 new_pipereg = Signal.like(value, name=rname, reset_less = True)
390 if next_stage not in self._pipeline_register_map:
391 self._pipeline_register_map[next_stage] = {}
392 self._pipeline_register_map[next_stage][name] = new_pipereg
393 self._m.d.sync += eq(new_pipereg, value)
394