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