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