1 # SPDX-License-Identifier: LGPL-3-or-later
3 This work is funded through NLnet under Grant 2019-02-012
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
20 """ Example 5: Making use of PyRTL and Introspection.
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
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)
36 return Signal(value_bits_sign(value
), name
=rname
,
38 return Signal
.like(value
, name
=rname
, reset_less
=True)
41 def get_assigns(_assigns
):
44 if isinstance(e
, ObjectProxy
):
45 assigns
+= get_assigns(e
._assigns
)
54 if isinstance(e
, ObjectProxy
):
55 eqs
+= get_eqs(e
._eqs
)
62 def __init__(self
, m
, name
=None, pipemode
=False, syncmode
=True):
65 name
= tracer
.get_var_name(default
=None)
67 self
._pipemode
= pipemode
68 self
._syncmode
= syncmode
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
,
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():
84 r
._preg
_map
[aname
] = like(a
, aname
, m
, pipemode
)
89 for a
in self
.ports():
91 ai
= self
._preg
_map
[aname
]
92 subobjs
.append(repr(ai
))
93 return "<OP %s>" % subobjs
95 def get_specs(self
, liked
=False):
97 for k
, v
in self
._preg
_map
.items():
98 #v = like(v, k, stage._m)
100 if isinstance(v
, ObjectProxy
):
105 print("ObjectProxy eq", self
, i
)
107 for a
in self
.ports():
109 ai
= i
._preg
_map
[aname
]
115 for aname
, a
in self
._preg
_map
.items():
116 if isinstance(a
, Signal
) or isinstance(a
, ObjectProxy
) or \
117 isinstance(a
, Record
):
119 #print ("ObjectPorts", res)
122 def __getattr__(self
, name
):
124 v
= self
._preg
_map
[name
]
126 # return like(v, name, self._m)
128 raise AttributeError(
129 'error, no pipeline register "%s" defined for OP %s'
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
)
137 #rname = "%s_%s" % (self.name, 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)
143 #print ("OP pipemode", self._syncmode, new_pipereg, value)
144 assign
= eq(new_pipereg
, value
)
146 self
._m
.d
.sync
+= assign
148 self
._m
.d
.comb
+= assign
150 #print ("OP !pipemode assign", new_pipereg, value, type(value))
151 self
._m
.d
.comb
+= eq(new_pipereg
, value
)
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
)
162 """ Pipeline builder stage with auto generation of pipeline registers.
165 def __init__(self
, name
, m
, prev
=None, pipemode
=False, ispec
=None):
167 self
._stagename
= name
168 self
._preg
_map
= {'__nextstage__': {}}
169 self
._prev
_stage
= prev
172 self
._preg
_map
[self
._stagename
] = ispec
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__']
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
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]
195 print("getattr", name
, object.__getattribute
__(self
, '_preg_map'))
196 v
= self
._preg
_map
[self
._stagename
][name
]
198 # return like(v, name, self._m)
200 raise AttributeError(
201 'error, no pipeline register "%s" defined for stage %s'
202 % (name
, self
._stagename
))
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
)
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
)
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
228 print("!pipemode: assign", new_pipereg
, value
)
229 assign
= eq(new_pipereg
, value
)
230 self
._m
.d
.sync
+= assign
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
245 res
.append(like(v
, v
.name
, None, pipemode
=True))
250 if not isinstance(specs
, dict):
251 return like(specs
, specs
.name
, None, pipemode
=True)
253 for k
, v
in specs
.items():
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()
264 def ispec(self
): return likedict(self
.inspecs
)
265 def ospec(self
): return likedict(self
.outspecs
)
267 def process(self
, i
):
268 print("stage process", i
)
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)
282 class AutoPipe(UnbufferedPipeline
):
283 def __init__(self
, stage
, assigns
):
284 UnbufferedPipeline
.__init
__(self
, stage
)
285 self
.assigns
= assigns
287 def elaborate(self
, platform
):
288 m
= UnbufferedPipeline
.elaborate(self
, platform
)
289 m
.d
.comb
+= self
.assigns
290 print("assigns", self
.assigns
, m
)
295 def __init__(self
, m
, pipemode
=False, pipetype
=None):
297 self
.pipemode
= pipemode
298 self
.pipetype
= pipetype
301 def Stage(self
, name
, prev
=None, ispec
=None):
303 ispec
= likedict(ispec
)
304 print("start stage", name
, ispec
)
305 stage
= PipelineStage(name
, None, prev
, self
.pipemode
, ispec
=ispec
)
307 yield stage
, self
.m
# stage._m
312 print("use ispec", stage
._ispec
)
313 inspecs
= stage
._ispec
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")
328 def get_specs(self
, stage
, name
, liked
=False):
329 return stage
._preg
_map
[name
]
330 if name
in stage
._preg
_map
:
332 for k
, v
in stage
._preg
_map
[name
].items():
333 #v = like(v, k, stage._m)
335 # if isinstance(v, ObjectProxy):
336 # res += v.get_specs()
344 def __exit__(self
, *args
):
345 print("exit stage", args
)
348 for s
in self
.stages
:
349 print("stage specs", s
, s
.inspecs
, s
.outspecs
)
350 if self
.pipetype
== 'buffered':
351 p
= BufferedHandshake(s
)
353 p
= AutoPipe(s
, s
.assigns
)
355 self
.m
.submodules
+= p
357 self
.m
.d
.comb
+= cb
.connect(pipes
)
360 class SimplePipeline
:
361 """ Pipeline builder with auto generation of pipeline registers.
364 def __init__(self
, m
):
366 self
._pipeline
_register
_map
= {}
367 self
._current
_stage
_num
= 0
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
)
377 self
._current
_stage
_num
+= 1
379 def __getattr__(self
, name
):
381 return self
._pipeline
_register
_map
[self
._current
_stage
_num
][name
]
383 raise AttributeError(
384 'error, no pipeline register "%s" defined for stage %d'
385 % (name
, self
._current
_stage
_num
))
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
)
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,
397 if isinstance(value
, ObjectProxy
):
398 new_pipereg
= ObjectProxy
.like(self
._m
, value
,
399 name
=rname
, reset_less
=True)
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
)