continuing to experiment with pipeline, outputs still not connected
[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 eq(self, i):
85 print ("ObjectProxy eq", self, i)
86 res = []
87 for a in self.ports():
88 aname = a.name
89 ai = i._preg_map[aname]
90 res.append(a.eq(ai))
91 return res
92
93 def ports(self):
94 res = []
95 for aname, a in self._preg_map.items():
96 if isinstance(a, Signal) or isinstance(a, ObjectProxy) or \
97 isinstance(a, Record):
98 res.append(a)
99 #print ("ObjectPorts", res)
100 return res
101
102 def __getattr__(self, name):
103 try:
104 v = self._preg_map[name]
105 return v
106 #return like(v, name, self._m)
107 except KeyError:
108 raise AttributeError(
109 'error, no pipeline register "%s" defined for OP %s'
110 % (name, self.name))
111
112 def __setattr__(self, name, value):
113 if name.startswith('_') or name in ['name', 'ports', 'eq', 'like']:
114 # do not do anything tricky with variables starting with '_'
115 object.__setattr__(self, name, value)
116 return
117 #rname = "%s_%s" % (self.name, name)
118 rname = name
119 new_pipereg = like(value, rname, self._m, self._pipemode)
120 self._preg_map[name] = new_pipereg
121 #object.__setattr__(self, name, new_pipereg)
122 if self._pipemode:
123 print ("OP pipemode", new_pipereg, value)
124 #self._eqs.append(value)
125 #self._m.d.comb += eq(new_pipereg, value)
126 pass
127 elif self._m:
128 print ("OP !pipemode assign", new_pipereg, value, type(value))
129 self._m.d.comb += eq(new_pipereg, value)
130 else:
131 print ("OP !pipemode !m", new_pipereg, value, type(value))
132 self._assigns += eq(new_pipereg, value)
133
134
135 class PipelineStage:
136 """ Pipeline builder stage with auto generation of pipeline registers.
137 """
138
139 def __init__(self, name, m, prev=None, pipemode=False, ispec=None):
140 self._m = m
141 self._stagename = name
142 self._preg_map = {}
143 self._prev_stage = prev
144 self._ispec = ispec
145 if prev:
146 print ("prev", prev._stagename, prev._preg_map)
147 if prev._stagename in prev._preg_map:
148 m = prev._preg_map[prev._stagename]
149 self._preg_map[prev._stagename] = m
150 #for k, v in m.items():
151 #m[k] = like(v, k, self._m)
152 if '__nextstage__' in prev._preg_map:
153 m = prev._preg_map['__nextstage__']
154 self._preg_map[self._stagename] = m
155 #for k, v in m.items():
156 #m[k] = like(v, k, self._m)
157 print ("make current", self._stagename, m)
158 self._pipemode = pipemode
159 self._eqs = []
160 self._assigns = []
161
162 def __getattr__(self, name):
163 try:
164 v = self._preg_map[self._stagename][name]
165 return v
166 #return like(v, name, self._m)
167 except KeyError:
168 raise AttributeError(
169 'error, no pipeline register "%s" defined for stage %s'
170 % (name, self._stagename))
171
172 def __setattr__(self, name, value):
173 if name.startswith('_'):
174 # do not do anything tricky with variables starting with '_'
175 object.__setattr__(self, name, value)
176 return
177 pipereg_id = self._stagename
178 rname = 'pipereg_' + pipereg_id + '_' + name
179 new_pipereg = like(value, rname, self._m, self._pipemode)
180 next_stage = '__nextstage__'
181 if next_stage not in self._preg_map:
182 self._preg_map[next_stage] = {}
183 self._preg_map[next_stage][name] = new_pipereg
184 if self._pipemode:
185 self._eqs.append(value)
186 assign = eq(new_pipereg, value)
187 print ("pipemode: append", new_pipereg, value, assign)
188 if isinstance(value, ObjectProxy):
189 print ("OP, assigns:", value._assigns)
190 self._assigns += value._assigns
191 #self._eqs += value._eqs
192 #self._m.d.comb += assign
193 self._assigns += assign
194 elif self._m:
195 print ("!pipemode: assign", new_pipereg, value)
196 assign = eq(new_pipereg, value)
197 self._m.d.sync += assign
198 else:
199 print ("!pipemode !m: defer assign", new_pipereg, value)
200 assign = eq(new_pipereg, value)
201 self._assigns += assign
202 if isinstance(value, ObjectProxy):
203 print ("OP, defer assigns:", value._assigns)
204 self._assigns += value._assigns
205
206 def likelist(specs):
207 res = []
208 for v in specs:
209 res.append(like(v, v.name, None, pipemode=True))
210 return res
211
212 class AutoStage(StageCls):
213 def __init__(self, inspecs, outspecs, eqs, assigns):
214 self.inspecs, self.outspecs = inspecs, outspecs
215 self.eqs, self.assigns = eqs, assigns
216 #self.o = self.ospec()
217 def ispec(self): return likelist(self.inspecs)
218 def ospec(self): return likelist(self.outspecs)
219
220 def process(self, i):
221 print ("stage process", i)
222 return self.eqs
223
224 def setup(self, m, i):
225 #self.o = self.ospec()
226 m.d.comb += eq(self.inspecs, i)
227 print ("stage setup", i)
228 print ("stage setup inspecs", self.inspecs)
229 #m.d.comb += eq(self.o, i)
230
231
232 class AutoPipe(UnbufferedPipeline):
233 def __init__(self, stage, assigns):
234 UnbufferedPipeline.__init__(self, stage)
235 self.assigns = assigns
236
237 def elaborate(self, platform):
238 m = UnbufferedPipeline.elaborate(self, platform)
239 m.d.comb += self.assigns
240 print ("assigns", self.assigns)
241 return m
242
243
244 class PipeManager:
245 def __init__(self, m, pipemode=False, pipetype=None):
246 self.m = m
247 self.pipemode = pipemode
248 self.pipetype = pipetype
249
250 @contextmanager
251 def Stage(self, name, prev=None, ispec=None):
252 print ("start stage", name)
253 if ispec:
254 ispec = likelist(ispec)
255 stage = PipelineStage(name, None, prev, self.pipemode, ispec=ispec)
256 try:
257 yield stage, self.m #stage._m
258 finally:
259 pass
260 if self.pipemode:
261 if stage._ispec:
262 print ("use ispec", stage._ispec)
263 inspecs = stage._ispec
264 else:
265 inspecs = self.get_specs(stage, name)
266 inspecs = likelist(inspecs)
267 outspecs = self.get_specs(stage, '__nextstage__', liked=True)
268 eqs = get_eqs(stage._eqs)
269 assigns = get_assigns(stage._assigns)
270 print ("stage eqs", name, eqs)
271 print ("stage assigns", name, assigns)
272 s = AutoStage(inspecs, outspecs, eqs, assigns)
273 self.stages.append(s)
274 print ("end stage", name, self.pipemode, "\n")
275
276 def get_specs(self, stage, name, liked=False):
277 if name in stage._preg_map:
278 res = []
279 for k, v in stage._preg_map[name].items():
280 #v = like(v, k, stage._m)
281 res.append(v)
282 return res
283 return []
284
285 def __enter__(self):
286 self.stages = []
287 return self
288
289 def __exit__(self, *args):
290 print ("exit stage", args)
291 pipes = []
292 cb = ControlBase()
293 for s in self.stages:
294 print ("stage specs", s, s.inspecs, s.outspecs)
295 if self.pipetype == 'buffered':
296 p = BufferedPipeline(s)
297 else:
298 p = AutoPipe(s, s.assigns)
299 pipes.append(p)
300 self.m.submodules += p
301
302 self.m.d.comb += cb.connect(pipes)
303
304
305 class SimplePipeline:
306 """ Pipeline builder with auto generation of pipeline registers.
307 """
308
309 def __init__(self, m):
310 self._m = m
311 self._pipeline_register_map = {}
312 self._current_stage_num = 0
313
314 def _setup(self):
315 stage_list = []
316 for method in dir(self):
317 if method.startswith('stage'):
318 stage_list.append(method)
319 for stage in sorted(stage_list):
320 stage_method = getattr(self, stage)
321 stage_method()
322 self._current_stage_num += 1
323
324 def __getattr__(self, name):
325 try:
326 return self._pipeline_register_map[self._current_stage_num][name]
327 except KeyError:
328 raise AttributeError(
329 'error, no pipeline register "%s" defined for stage %d'
330 % (name, self._current_stage_num))
331
332 def __setattr__(self, name, value):
333 if name.startswith('_'):
334 # do not do anything tricky with variables starting with '_'
335 object.__setattr__(self, name, value)
336 return
337 next_stage = self._current_stage_num + 1
338 pipereg_id = str(self._current_stage_num) + 'to' + str(next_stage)
339 rname = 'pipereg_' + pipereg_id + '_' + name
340 #new_pipereg = Signal(value_bits_sign(value), name=rname,
341 # reset_less=True)
342 if isinstance(value, ObjectProxy):
343 new_pipereg = ObjectProxy.like(self._m, value,
344 name=rname, reset_less = True)
345 else:
346 new_pipereg = Signal.like(value, name=rname, reset_less = True)
347 if next_stage not in self._pipeline_register_map:
348 self._pipeline_register_map[next_stage] = {}
349 self._pipeline_register_map[next_stage][name] = new_pipereg
350 self._m.d.sync += eq(new_pipereg, value)
351