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