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