more experimentation on pipeline ObjectProxy
[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_eqs(_eqs):
32 eqs = []
33 for e in _eqs:
34 if isinstance(e, ObjectProxy):
35 eqs += get_eqs(e._eqs)
36 else:
37 eqs.append(e)
38 return eqs
39
40
41 class ObjectProxy:
42 def __init__(self, m, name=None, pipemode=False):
43 self._m = m
44 if name is None:
45 name = tracer.get_var_name(default=None)
46 self.name = name
47 self._pipemode = pipemode
48 self._eqs = []
49 self._preg_map = {}
50
51 @classmethod
52 def like(cls, m, value, pipemode=False, name=None, src_loc_at=0, **kwargs):
53 name = name or tracer.get_var_name(depth=2 + src_loc_at,
54 default="$like")
55
56 src_loc_at_1 = 1 + src_loc_at
57 r = ObjectProxy(m, value.name, pipemode)
58 #for a, aname in value._preg_map.items():
59 # r._preg_map[aname] = like(a, aname, m, pipemode)
60 for a in value.ports():
61 aname = a.name
62 r._preg_map[aname] = like(a, aname, m, pipemode)
63 return r
64
65 def __repr__(self):
66 subobjs = []
67 for a in self.ports():
68 aname = a.name
69 ai = self._preg_map[aname]
70 subobjs.append(repr(ai))
71 return "<OP %s>" % subobjs
72
73 def eq(self, i):
74 print ("ObjectProxy eq", self, i)
75 res = []
76 for a in self.ports():
77 aname = a.name
78 ai = i._preg_map[aname]
79 res.append(a.eq(ai))
80 return res
81
82 def ports(self):
83 res = []
84 for aname, a in self._preg_map.items():
85 if isinstance(a, Signal) or isinstance(a, ObjectProxy) or \
86 isinstance(a, Record):
87 res.append(a)
88 print ("ObjectPorts", res)
89 return res
90
91 def __getattr__(self, name):
92 try:
93 v = self._preg_map[name]
94 return v
95 #return like(v, name, self._m)
96 except KeyError:
97 raise AttributeError(
98 'error, no pipeline register "%s" defined for OP %s'
99 % (name, self.name))
100
101 def __setattr__(self, name, value):
102 if name.startswith('_') or name in ['name', 'ports', 'eq', 'like']:
103 # do not do anything tricky with variables starting with '_'
104 object.__setattr__(self, name, value)
105 return
106 #rname = "%s_%s" % (self.name, name)
107 rname = name
108 new_pipereg = like(value, rname, self._m, self._pipemode)
109 self._preg_map[name] = new_pipereg
110 #object.__setattr__(self, name, new_pipereg)
111 if self._pipemode:
112 print ("OP pipemode", new_pipereg, value)
113 #self._eqs.append(value)
114 #self._m.d.comb += eq(new_pipereg, value)
115 pass
116 elif self._m:
117 print ("OP !pipemode assign", new_pipereg, value, type(value))
118 self._m.d.comb += eq(new_pipereg, value)
119
120
121 class PipelineStage:
122 """ Pipeline builder stage with auto generation of pipeline registers.
123 """
124
125 def __init__(self, name, m, prev=None, pipemode=False, ispec=None):
126 self._m = m
127 self._stagename = name
128 self._preg_map = {}
129 self._prev_stage = prev
130 self._ispec = ispec
131 if prev:
132 print ("prev", prev._stagename, prev._preg_map)
133 if prev._stagename in prev._preg_map:
134 m = prev._preg_map[prev._stagename]
135 self._preg_map[prev._stagename] = m
136 #for k, v in m.items():
137 #m[k] = like(v, k, self._m)
138 if '__nextstage__' in prev._preg_map:
139 m = prev._preg_map['__nextstage__']
140 self._preg_map[self._stagename] = m
141 #for k, v in m.items():
142 #m[k] = like(v, k, self._m)
143 print ("make current", self._stagename, m)
144 self._pipemode = pipemode
145 self._eqs = []
146
147 def __getattr__(self, name):
148 try:
149 v = self._preg_map[self._stagename][name]
150 return v
151 #return like(v, name, self._m)
152 except KeyError:
153 raise AttributeError(
154 'error, no pipeline register "%s" defined for stage %s'
155 % (name, self._stagename))
156
157 def __setattr__(self, name, value):
158 if name.startswith('_'):
159 # do not do anything tricky with variables starting with '_'
160 object.__setattr__(self, name, value)
161 return
162 pipereg_id = self._stagename
163 rname = 'pipereg_' + pipereg_id + '_' + name
164 new_pipereg = like(value, rname, self._m, self._pipemode)
165 next_stage = '__nextstage__'
166 if next_stage not in self._preg_map:
167 self._preg_map[next_stage] = {}
168 self._preg_map[next_stage][name] = new_pipereg
169 if self._pipemode:
170 self._eqs.append(value)
171 print ("pipemode: append", new_pipereg, value)
172 #self._m.d.comb += assign
173 else:
174 print ("!pipemode: assign", new_pipereg, value)
175 assign = eq(new_pipereg, value)
176 self._m.d.sync += assign
177
178
179 class AutoStage(StageCls):
180 def __init__(self, inspecs, outspecs, eqs):
181 self.inspecs, self.outspecs, self.eqs = inspecs, outspecs, eqs
182 self.o = self.ospec()
183 def ispec(self): return self.like(self.inspecs)
184 def ospec(self): return self.like(self.outspecs)
185 def like(self, specs):
186 res = []
187 for v in specs:
188 res.append(like(v, v.name, None, pipemode=True))
189 return res
190
191 def process(self, i):
192 print ("stage process", i)
193 return self.o
194
195 def setup(self, m, i):
196 print ("stage setup", i)
197 m.d.sync += eq(i, self.eqs)
198 m.d.comb += eq(self.o, i)
199
200
201 class PipeManager:
202 def __init__(self, m, pipemode=False, pipetype=None):
203 self.m = m
204 self.pipemode = pipemode
205 self.pipetype = pipetype
206
207 @contextmanager
208 def Stage(self, name, prev=None, ispec=None):
209 print ("start stage", name)
210 stage = PipelineStage(name, self.m, prev, self.pipemode, ispec=ispec)
211 try:
212 yield stage, stage._m
213 finally:
214 pass
215 if self.pipemode:
216 if stage._ispec:
217 print ("use ispec", stage._ispec)
218 inspecs = stage._ispec
219 else:
220 inspecs = self.get_specs(stage, name)
221 outspecs = self.get_specs(stage, '__nextstage__', liked=True)
222 eqs = get_eqs(stage._eqs)
223 print ("stage eqs", name, eqs)
224 s = AutoStage(inspecs, outspecs, eqs)
225 self.stages.append(s)
226 print ("end stage", name, "\n")
227
228 def get_specs(self, stage, name, liked=False):
229 if name in stage._preg_map:
230 res = []
231 for k, v in stage._preg_map[name].items():
232 #v = like(v, k, stage._m)
233 res.append(v)
234 return res
235 return []
236
237 def __enter__(self):
238 self.stages = []
239 return self
240
241 def __exit__(self, *args):
242 print ("exit stage", args)
243 pipes = []
244 cb = ControlBase()
245 for s in self.stages:
246 print ("stage specs", s, s.inspecs, s.outspecs)
247 if self.pipetype == 'buffered':
248 p = BufferedPipeline(s)
249 else:
250 p = UnbufferedPipeline(s)
251 pipes.append(p)
252 self.m.submodules += p
253
254 self.m.d.comb += cb.connect(pipes)
255
256
257 class SimplePipeline:
258 """ Pipeline builder with auto generation of pipeline registers.
259 """
260
261 def __init__(self, m):
262 self._m = m
263 self._pipeline_register_map = {}
264 self._current_stage_num = 0
265
266 def _setup(self):
267 stage_list = []
268 for method in dir(self):
269 if method.startswith('stage'):
270 stage_list.append(method)
271 for stage in sorted(stage_list):
272 stage_method = getattr(self, stage)
273 stage_method()
274 self._current_stage_num += 1
275
276 def __getattr__(self, name):
277 try:
278 return self._pipeline_register_map[self._current_stage_num][name]
279 except KeyError:
280 raise AttributeError(
281 'error, no pipeline register "%s" defined for stage %d'
282 % (name, self._current_stage_num))
283
284 def __setattr__(self, name, value):
285 if name.startswith('_'):
286 # do not do anything tricky with variables starting with '_'
287 object.__setattr__(self, name, value)
288 return
289 next_stage = self._current_stage_num + 1
290 pipereg_id = str(self._current_stage_num) + 'to' + str(next_stage)
291 rname = 'pipereg_' + pipereg_id + '_' + name
292 #new_pipereg = Signal(value_bits_sign(value), name=rname,
293 # reset_less=True)
294 if isinstance(value, ObjectProxy):
295 new_pipereg = ObjectProxy.like(self._m, value,
296 name=rname, reset_less = True)
297 else:
298 new_pipereg = Signal.like(value, name=rname, reset_less = True)
299 if next_stage not in self._pipeline_register_map:
300 self._pipeline_register_map[next_stage] = {}
301 self._pipeline_register_map[next_stage][name] = new_pipereg
302 self._m.d.sync += eq(new_pipereg, value)
303