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