ded2e03dbc55dba55bff72d46bcb858eb8289744
[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 from collections.abc import Sequence
10 from nmigen import Signal
11 from nmigen.hdl.rec import Record
12 from nmigen import tracer
13 from nmigen.compat.fhdl.bitcontainer import value_bits_sign
14 from contextlib import contextmanager
15 from nmutil.nmoperator import eq
16 from nmutil.singlepipe import StageCls, ControlBase, BufferedHandshake
17 from nmutil.singlepipe import UnbufferedPipeline
18
19 """ Example 5: Making use of PyRTL and Introspection.
20
21 The following example shows how pyrtl can be used to make some interesting
22 hardware structures using python introspection. In particular, this example
23 makes a N-stage pipeline structure. Any specific pipeline is then a derived
24 class of SimplePipeline where methods with names starting with "stage" are
25 stages, and new members with names not starting with "_" are to be registered
26 for the next stage.
27 """
28
29
30 def like(value, rname, pipe, pipemode=False):
31 if isinstance(value, ObjectProxy):
32 return ObjectProxy.like(pipe, value, pipemode=pipemode,
33 name=rname, reset_less=True)
34 else:
35 return Signal(value_bits_sign(value), name=rname,
36 reset_less=True)
37 return Signal.like(value, name=rname, reset_less=True)
38
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
241 def likelist(specs):
242 res = []
243 for v in specs:
244 res.append(like(v, v.name, None, pipemode=True))
245 return res
246
247
248 def likedict(specs):
249 if not isinstance(specs, dict):
250 return like(specs, specs.name, None, pipemode=True)
251 res = {}
252 for k, v in specs.items():
253 res[k] = likedict(v)
254 return res
255
256
257 class AutoStage(StageCls):
258 def __init__(self, inspecs, outspecs, eqs, assigns):
259 self.inspecs, self.outspecs = inspecs, outspecs
260 self.eqs, self.assigns = eqs, assigns
261 #self.o = self.ospec()
262
263 def ispec(self): return likedict(self.inspecs)
264 def ospec(self): return likedict(self.outspecs)
265
266 def process(self, i):
267 print("stage process", i)
268 return self.eqs
269
270 def setup(self, m, i):
271 print("stage setup i", i, m)
272 print("stage setup inspecs", self.inspecs)
273 print("stage setup outspecs", self.outspecs)
274 print("stage setup eqs", self.eqs)
275 #self.o = self.ospec()
276 m.d.comb += eq(self.inspecs, i)
277 #m.d.comb += eq(self.outspecs, self.eqs)
278 #m.d.comb += eq(self.o, i)
279
280
281 class AutoPipe(UnbufferedPipeline):
282 def __init__(self, stage, assigns):
283 UnbufferedPipeline.__init__(self, stage)
284 self.assigns = assigns
285
286 def elaborate(self, platform):
287 m = UnbufferedPipeline.elaborate(self, platform)
288 m.d.comb += self.assigns
289 print("assigns", self.assigns, m)
290 return m
291
292
293 class PipeManager:
294 def __init__(self, m, pipemode=False, pipetype=None):
295 self.m = m
296 self.pipemode = pipemode
297 self.pipetype = pipetype
298
299 @contextmanager
300 def Stage(self, name, prev=None, ispec=None):
301 if ispec:
302 ispec = likedict(ispec)
303 print("start stage", name, ispec)
304 stage = PipelineStage(name, None, prev, self.pipemode, ispec=ispec)
305 try:
306 yield stage, self.m # stage._m
307 finally:
308 pass
309 if self.pipemode:
310 if stage._ispec:
311 print("use ispec", stage._ispec)
312 inspecs = stage._ispec
313 else:
314 inspecs = self.get_specs(stage, name)
315 #inspecs = likedict(inspecs)
316 outspecs = self.get_specs(stage, '__nextstage__', liked=True)
317 print("stage inspecs", name, inspecs)
318 print("stage outspecs", name, outspecs)
319 eqs = stage._eqs # get_eqs(stage._eqs)
320 assigns = get_assigns(stage._assigns)
321 print("stage eqs", name, eqs)
322 print("stage assigns", name, assigns)
323 s = AutoStage(inspecs, outspecs, eqs, assigns)
324 self.stages.append(s)
325 print("end stage", name, self.pipemode, "\n")
326
327 def get_specs(self, stage, name, liked=False):
328 return stage._preg_map[name]
329 if name in stage._preg_map:
330 res = []
331 for k, v in stage._preg_map[name].items():
332 #v = like(v, k, stage._m)
333 res.append(v)
334 # if isinstance(v, ObjectProxy):
335 # res += v.get_specs()
336 return res
337 return {}
338
339 def __enter__(self):
340 self.stages = []
341 return self
342
343 def __exit__(self, *args):
344 print("exit stage", args)
345 pipes = []
346 cb = ControlBase()
347 for s in self.stages:
348 print("stage specs", s, s.inspecs, s.outspecs)
349 if self.pipetype == 'buffered':
350 p = BufferedHandshake(s)
351 else:
352 p = AutoPipe(s, s.assigns)
353 pipes.append(p)
354 self.m.submodules += p
355
356 self.m.d.comb += cb.connect(pipes)
357
358
359 class SimplePipeline:
360 """ Pipeline builder with auto generation of pipeline registers.
361 """
362
363 def __init__(self, m):
364 self._m = m
365 self._pipeline_register_map = {}
366 self._current_stage_num = 0
367
368 def _setup(self):
369 stage_list = []
370 for method in dir(self):
371 if method.startswith('stage'):
372 stage_list.append(method)
373 for stage in sorted(stage_list):
374 stage_method = getattr(self, stage)
375 stage_method()
376 self._current_stage_num += 1
377
378 def __getattr__(self, name):
379 try:
380 return self._pipeline_register_map[self._current_stage_num][name]
381 except KeyError:
382 raise AttributeError(
383 'error, no pipeline register "%s" defined for stage %d'
384 % (name, self._current_stage_num))
385
386 def __setattr__(self, name, value):
387 if name.startswith('_'):
388 # do not do anything tricky with variables starting with '_'
389 object.__setattr__(self, name, value)
390 return
391 next_stage = self._current_stage_num + 1
392 pipereg_id = str(self._current_stage_num) + 'to' + str(next_stage)
393 rname = 'pipereg_' + pipereg_id + '_' + name
394 # new_pipereg = Signal(value_bits_sign(value), name=rname,
395 # reset_less=True)
396 if isinstance(value, ObjectProxy):
397 new_pipereg = ObjectProxy.like(self._m, value,
398 name=rname, reset_less=True)
399 else:
400 new_pipereg = Signal.like(value, name=rname, reset_less=True)
401 if next_stage not in self._pipeline_register_map:
402 self._pipeline_register_map[next_stage] = {}
403 self._pipeline_register_map[next_stage][name] = new_pipereg
404 self._m.d.sync += eq(new_pipereg, value)