SimObject.py (13709:dd6b7ac5801f) SimObject.py (13714:35636064b7a1)
1# Copyright (c) 2017-2018 ARM Limited
2# All rights reserved.
3#
4# The license below extends only to copyright in the software and shall
5# not be construed as granting a license to any other intellectual
6# property including but not limited to intellectual property relating
7# to a hardware implementation of the functionality of the software
8# licensed hereunder. You may use the software subject to the license
9# terms below provided that you ensure that this notice is replicated
10# unmodified and in its entirety in all distributions of the software,
11# modified or unmodified, in source code or in binary form.
12#
13# Copyright (c) 2004-2006 The Regents of The University of Michigan
14# Copyright (c) 2010-20013 Advanced Micro Devices, Inc.
15# Copyright (c) 2013 Mark D. Hill and David A. Wood
16# All rights reserved.
17#
18# Redistribution and use in source and binary forms, with or without
19# modification, are permitted provided that the following conditions are
20# met: redistributions of source code must retain the above copyright
21# notice, this list of conditions and the following disclaimer;
22# redistributions in binary form must reproduce the above copyright
23# notice, this list of conditions and the following disclaimer in the
24# documentation and/or other materials provided with the distribution;
25# neither the name of the copyright holders nor the names of its
26# contributors may be used to endorse or promote products derived from
27# this software without specific prior written permission.
28#
29# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40#
41# Authors: Steve Reinhardt
42# Nathan Binkert
43# Andreas Hansson
44# Andreas Sandberg
45
46from __future__ import print_function
1# Copyright (c) 2017-2018 ARM Limited
2# All rights reserved.
3#
4# The license below extends only to copyright in the software and shall
5# not be construed as granting a license to any other intellectual
6# property including but not limited to intellectual property relating
7# to a hardware implementation of the functionality of the software
8# licensed hereunder. You may use the software subject to the license
9# terms below provided that you ensure that this notice is replicated
10# unmodified and in its entirety in all distributions of the software,
11# modified or unmodified, in source code or in binary form.
12#
13# Copyright (c) 2004-2006 The Regents of The University of Michigan
14# Copyright (c) 2010-20013 Advanced Micro Devices, Inc.
15# Copyright (c) 2013 Mark D. Hill and David A. Wood
16# All rights reserved.
17#
18# Redistribution and use in source and binary forms, with or without
19# modification, are permitted provided that the following conditions are
20# met: redistributions of source code must retain the above copyright
21# notice, this list of conditions and the following disclaimer;
22# redistributions in binary form must reproduce the above copyright
23# notice, this list of conditions and the following disclaimer in the
24# documentation and/or other materials provided with the distribution;
25# neither the name of the copyright holders nor the names of its
26# contributors may be used to endorse or promote products derived from
27# this software without specific prior written permission.
28#
29# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40#
41# Authors: Steve Reinhardt
42# Nathan Binkert
43# Andreas Hansson
44# Andreas Sandberg
45
46from __future__ import print_function
47from __future__ import absolute_import
47
48import sys
49from types import FunctionType, MethodType, ModuleType
50from functools import wraps
51import inspect
52
53import m5
54from m5.util import *
55from m5.util.pybind import *
56# Use the pyfdt and not the helper class, because the fdthelper
57# relies on the SimObject definition
58from m5.ext.pyfdt import pyfdt
59
60# Have to import params up top since Param is referenced on initial
61# load (when SimObject class references Param to create a class
62# variable, the 'name' param)...
63from m5.params import *
64# There are a few things we need that aren't in params.__all__ since
65# normal users don't need them
66from m5.params import ParamDesc, VectorParamDesc, \
67 isNullPointer, SimObjectVector, Port
68
69from m5.proxy import *
70from m5.proxy import isproxy
71
72#####################################################################
73#
74# M5 Python Configuration Utility
75#
76# The basic idea is to write simple Python programs that build Python
77# objects corresponding to M5 SimObjects for the desired simulation
78# configuration. For now, the Python emits a .ini file that can be
79# parsed by M5. In the future, some tighter integration between M5
80# and the Python interpreter may allow bypassing the .ini file.
81#
82# Each SimObject class in M5 is represented by a Python class with the
83# same name. The Python inheritance tree mirrors the M5 C++ tree
84# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
85# SimObjects inherit from a single SimObject base class). To specify
86# an instance of an M5 SimObject in a configuration, the user simply
87# instantiates the corresponding Python object. The parameters for
88# that SimObject are given by assigning to attributes of the Python
89# object, either using keyword assignment in the constructor or in
90# separate assignment statements. For example:
91#
92# cache = BaseCache(size='64KB')
93# cache.hit_latency = 3
94# cache.assoc = 8
95#
96# The magic lies in the mapping of the Python attributes for SimObject
97# classes to the actual SimObject parameter specifications. This
98# allows parameter validity checking in the Python code. Continuing
99# the example above, the statements "cache.blurfl=3" or
100# "cache.assoc='hello'" would both result in runtime errors in Python,
101# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
102# parameter requires an integer, respectively. This magic is done
103# primarily by overriding the special __setattr__ method that controls
104# assignment to object attributes.
105#
106# Once a set of Python objects have been instantiated in a hierarchy,
107# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
108# will generate a .ini file.
109#
110#####################################################################
111
112# list of all SimObject classes
113allClasses = {}
114
115# dict to look up SimObjects based on path
116instanceDict = {}
117
118# Did any of the SimObjects lack a header file?
119noCxxHeader = False
120
121def public_value(key, value):
122 return key.startswith('_') or \
123 isinstance(value, (FunctionType, MethodType, ModuleType,
124 classmethod, type))
125
126def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header):
127 entry_class = 'CxxConfigDirectoryEntry_%s' % name
128 param_class = '%sCxxConfigParams' % name
129
130 code('#include "params/%s.hh"' % name)
131
132 if not is_header:
133 for param in simobj._params.values():
134 if isSimObjectClass(param.ptype):
135 code('#include "%s"' % param.ptype._value_dict['cxx_header'])
136 code('#include "params/%s.hh"' % param.ptype.__name__)
137 else:
138 param.ptype.cxx_ini_predecls(code)
139
140 if is_header:
141 member_prefix = ''
142 end_of_decl = ';'
143 code('#include "sim/cxx_config.hh"')
144 code()
145 code('class ${param_class} : public CxxConfigParams,'
146 ' public ${name}Params')
147 code('{')
148 code(' private:')
149 code.indent()
150 code('class DirectoryEntry : public CxxConfigDirectoryEntry')
151 code('{')
152 code(' public:')
153 code.indent()
154 code('DirectoryEntry();');
155 code()
156 code('CxxConfigParams *makeParamsObject() const')
157 code('{ return new ${param_class}; }')
158 code.dedent()
159 code('};')
160 code()
161 code.dedent()
162 code(' public:')
163 code.indent()
164 else:
165 member_prefix = '%s::' % param_class
166 end_of_decl = ''
167 code('#include "%s"' % simobj._value_dict['cxx_header'])
168 code('#include "base/str.hh"')
169 code('#include "cxx_config/${name}.hh"')
170
171 if simobj._ports:
172 code('#include "mem/mem_object.hh"')
173 code('#include "mem/port.hh"')
174
175 code()
176 code('${member_prefix}DirectoryEntry::DirectoryEntry()');
177 code('{')
178
179 def cxx_bool(b):
180 return 'true' if b else 'false'
181
182 code.indent()
183 for param in simobj._params.values():
184 is_vector = isinstance(param, m5.params.VectorParamDesc)
185 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
186
187 code('parameters["%s"] = new ParamDesc("%s", %s, %s);' %
188 (param.name, param.name, cxx_bool(is_vector),
189 cxx_bool(is_simobj)));
190
191 for port in simobj._ports.values():
192 is_vector = isinstance(port, m5.params.VectorPort)
193 is_master = port.role == 'MASTER'
194
195 code('ports["%s"] = new PortDesc("%s", %s, %s);' %
196 (port.name, port.name, cxx_bool(is_vector),
197 cxx_bool(is_master)))
198
199 code.dedent()
200 code('}')
201 code()
202
203 code('bool ${member_prefix}setSimObject(const std::string &name,')
204 code(' SimObject *simObject)${end_of_decl}')
205
206 if not is_header:
207 code('{')
208 code.indent()
209 code('bool ret = true;')
210 code()
211 code('if (false) {')
212 for param in simobj._params.values():
213 is_vector = isinstance(param, m5.params.VectorParamDesc)
214 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
215
216 if is_simobj and not is_vector:
217 code('} else if (name == "${{param.name}}") {')
218 code.indent()
219 code('this->${{param.name}} = '
220 'dynamic_cast<${{param.ptype.cxx_type}}>(simObject);')
221 code('if (simObject && !this->${{param.name}})')
222 code(' ret = false;')
223 code.dedent()
224 code('} else {')
225 code(' ret = false;')
226 code('}')
227 code()
228 code('return ret;')
229 code.dedent()
230 code('}')
231
232 code()
233 code('bool ${member_prefix}setSimObjectVector('
234 'const std::string &name,')
235 code(' const std::vector<SimObject *> &simObjects)${end_of_decl}')
236
237 if not is_header:
238 code('{')
239 code.indent()
240 code('bool ret = true;')
241 code()
242 code('if (false) {')
243 for param in simobj._params.values():
244 is_vector = isinstance(param, m5.params.VectorParamDesc)
245 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
246
247 if is_simobj and is_vector:
248 code('} else if (name == "${{param.name}}") {')
249 code.indent()
250 code('this->${{param.name}}.clear();')
251 code('for (auto i = simObjects.begin(); '
252 'ret && i != simObjects.end(); i ++)')
253 code('{')
254 code.indent()
255 code('${{param.ptype.cxx_type}} object = '
256 'dynamic_cast<${{param.ptype.cxx_type}}>(*i);')
257 code('if (*i && !object)')
258 code(' ret = false;')
259 code('else')
260 code(' this->${{param.name}}.push_back(object);')
261 code.dedent()
262 code('}')
263 code.dedent()
264 code('} else {')
265 code(' ret = false;')
266 code('}')
267 code()
268 code('return ret;')
269 code.dedent()
270 code('}')
271
272 code()
273 code('void ${member_prefix}setName(const std::string &name_)'
274 '${end_of_decl}')
275
276 if not is_header:
277 code('{')
278 code.indent()
279 code('this->name = name_;')
280 code.dedent()
281 code('}')
282
283 if is_header:
284 code('const std::string &${member_prefix}getName()')
285 code('{ return this->name; }')
286
287 code()
288 code('bool ${member_prefix}setParam(const std::string &name,')
289 code(' const std::string &value, const Flags flags)${end_of_decl}')
290
291 if not is_header:
292 code('{')
293 code.indent()
294 code('bool ret = true;')
295 code()
296 code('if (false) {')
297 for param in simobj._params.values():
298 is_vector = isinstance(param, m5.params.VectorParamDesc)
299 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
300
301 if not is_simobj and not is_vector:
302 code('} else if (name == "${{param.name}}") {')
303 code.indent()
304 param.ptype.cxx_ini_parse(code,
305 'value', 'this->%s' % param.name, 'ret =')
306 code.dedent()
307 code('} else {')
308 code(' ret = false;')
309 code('}')
310 code()
311 code('return ret;')
312 code.dedent()
313 code('}')
314
315 code()
316 code('bool ${member_prefix}setParamVector('
317 'const std::string &name,')
318 code(' const std::vector<std::string> &values,')
319 code(' const Flags flags)${end_of_decl}')
320
321 if not is_header:
322 code('{')
323 code.indent()
324 code('bool ret = true;')
325 code()
326 code('if (false) {')
327 for param in simobj._params.values():
328 is_vector = isinstance(param, m5.params.VectorParamDesc)
329 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
330
331 if not is_simobj and is_vector:
332 code('} else if (name == "${{param.name}}") {')
333 code.indent()
334 code('${{param.name}}.clear();')
335 code('for (auto i = values.begin(); '
336 'ret && i != values.end(); i ++)')
337 code('{')
338 code.indent()
339 code('${{param.ptype.cxx_type}} elem;')
340 param.ptype.cxx_ini_parse(code,
341 '*i', 'elem', 'ret =')
342 code('if (ret)')
343 code(' this->${{param.name}}.push_back(elem);')
344 code.dedent()
345 code('}')
346 code.dedent()
347 code('} else {')
348 code(' ret = false;')
349 code('}')
350 code()
351 code('return ret;')
352 code.dedent()
353 code('}')
354
355 code()
356 code('bool ${member_prefix}setPortConnectionCount('
357 'const std::string &name,')
358 code(' unsigned int count)${end_of_decl}')
359
360 if not is_header:
361 code('{')
362 code.indent()
363 code('bool ret = true;')
364 code()
365 code('if (false)')
366 code(' ;')
367 for port in simobj._ports.values():
368 code('else if (name == "${{port.name}}")')
369 code(' this->port_${{port.name}}_connection_count = count;')
370 code('else')
371 code(' ret = false;')
372 code()
373 code('return ret;')
374 code.dedent()
375 code('}')
376
377 code()
378 code('SimObject *${member_prefix}simObjectCreate()${end_of_decl}')
379
380 if not is_header:
381 code('{')
382 if hasattr(simobj, 'abstract') and simobj.abstract:
383 code(' return NULL;')
384 else:
385 code(' return this->create();')
386 code('}')
387
388 if is_header:
389 code()
390 code('static CxxConfigDirectoryEntry'
391 ' *${member_prefix}makeDirectoryEntry()')
392 code('{ return new DirectoryEntry; }')
393
394 if is_header:
395 code.dedent()
396 code('};')
397
398# The metaclass for SimObject. This class controls how new classes
399# that derive from SimObject are instantiated, and provides inherited
400# class behavior (just like a class controls how instances of that
401# class are instantiated, and provides inherited instance behavior).
402class MetaSimObject(type):
403 # Attributes that can be set only at initialization time
404 init_keywords = {
405 'abstract' : bool,
406 'cxx_class' : str,
407 'cxx_type' : str,
408 'cxx_header' : str,
409 'type' : str,
410 'cxx_base' : (str, type(None)),
411 'cxx_extra_bases' : list,
412 'cxx_exports' : list,
413 'cxx_param_exports' : list,
414 }
415 # Attributes that can be set any time
416 keywords = { 'check' : FunctionType }
417
418 # __new__ is called before __init__, and is where the statements
419 # in the body of the class definition get loaded into the class's
420 # __dict__. We intercept this to filter out parameter & port assignments
421 # and only allow "private" attributes to be passed to the base
422 # __new__ (starting with underscore).
423 def __new__(mcls, name, bases, dict):
424 assert name not in allClasses, "SimObject %s already present" % name
425
426 # Copy "private" attributes, functions, and classes to the
427 # official dict. Everything else goes in _init_dict to be
428 # filtered in __init__.
429 cls_dict = {}
430 value_dict = {}
431 cxx_exports = []
432 for key,val in dict.items():
433 try:
434 cxx_exports.append(getattr(val, "__pybind"))
435 except AttributeError:
436 pass
437
438 if public_value(key, val):
439 cls_dict[key] = val
440 else:
441 # must be a param/port setting
442 value_dict[key] = val
443 if 'abstract' not in value_dict:
444 value_dict['abstract'] = False
445 if 'cxx_extra_bases' not in value_dict:
446 value_dict['cxx_extra_bases'] = []
447 if 'cxx_exports' not in value_dict:
448 value_dict['cxx_exports'] = cxx_exports
449 else:
450 value_dict['cxx_exports'] += cxx_exports
451 if 'cxx_param_exports' not in value_dict:
452 value_dict['cxx_param_exports'] = []
453 cls_dict['_value_dict'] = value_dict
454 cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
455 if 'type' in value_dict:
456 allClasses[name] = cls
457 return cls
458
459 # subclass initialization
460 def __init__(cls, name, bases, dict):
461 # calls type.__init__()... I think that's a no-op, but leave
462 # it here just in case it's not.
463 super(MetaSimObject, cls).__init__(name, bases, dict)
464
465 # initialize required attributes
466
467 # class-only attributes
468 cls._params = multidict() # param descriptions
469 cls._ports = multidict() # port descriptions
470
471 # class or instance attributes
472 cls._values = multidict() # param values
473 cls._hr_values = multidict() # human readable param values
474 cls._children = multidict() # SimObject children
475 cls._port_refs = multidict() # port ref objects
476 cls._instantiated = False # really instantiated, cloned, or subclassed
477
478 # We don't support multiple inheritance of sim objects. If you want
479 # to, you must fix multidict to deal with it properly. Non sim-objects
480 # are ok, though
481 bTotal = 0
482 for c in bases:
483 if isinstance(c, MetaSimObject):
484 bTotal += 1
485 if bTotal > 1:
486 raise TypeError(
487 "SimObjects do not support multiple inheritance")
488
489 base = bases[0]
490
491 # Set up general inheritance via multidicts. A subclass will
492 # inherit all its settings from the base class. The only time
493 # the following is not true is when we define the SimObject
494 # class itself (in which case the multidicts have no parent).
495 if isinstance(base, MetaSimObject):
496 cls._base = base
497 cls._params.parent = base._params
498 cls._ports.parent = base._ports
499 cls._values.parent = base._values
500 cls._hr_values.parent = base._hr_values
501 cls._children.parent = base._children
502 cls._port_refs.parent = base._port_refs
503 # mark base as having been subclassed
504 base._instantiated = True
505 else:
506 cls._base = None
507
508 # default keyword values
509 if 'type' in cls._value_dict:
510 if 'cxx_class' not in cls._value_dict:
511 cls._value_dict['cxx_class'] = cls._value_dict['type']
512
513 cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class']
514
515 if 'cxx_header' not in cls._value_dict:
516 global noCxxHeader
517 noCxxHeader = True
518 warn("No header file specified for SimObject: %s", name)
519
520 # Now process the _value_dict items. They could be defining
521 # new (or overriding existing) parameters or ports, setting
522 # class keywords (e.g., 'abstract'), or setting parameter
523 # values or port bindings. The first 3 can only be set when
524 # the class is defined, so we handle them here. The others
525 # can be set later too, so just emulate that by calling
526 # setattr().
527 for key,val in cls._value_dict.items():
528 # param descriptions
529 if isinstance(val, ParamDesc):
530 cls._new_param(key, val)
531
532 # port objects
533 elif isinstance(val, Port):
534 cls._new_port(key, val)
535
536 # init-time-only keywords
537 elif key in cls.init_keywords:
538 cls._set_keyword(key, val, cls.init_keywords[key])
539
540 # default: use normal path (ends up in __setattr__)
541 else:
542 setattr(cls, key, val)
543
544 def _set_keyword(cls, keyword, val, kwtype):
545 if not isinstance(val, kwtype):
546 raise TypeError('keyword %s has bad type %s (expecting %s)' % \
547 (keyword, type(val), kwtype))
548 if isinstance(val, FunctionType):
549 val = classmethod(val)
550 type.__setattr__(cls, keyword, val)
551
552 def _new_param(cls, name, pdesc):
553 # each param desc should be uniquely assigned to one variable
554 assert(not hasattr(pdesc, 'name'))
555 pdesc.name = name
556 cls._params[name] = pdesc
557 if hasattr(pdesc, 'default'):
558 cls._set_param(name, pdesc.default, pdesc)
559
560 def _set_param(cls, name, value, param):
561 assert(param.name == name)
562 try:
563 hr_value = value
564 value = param.convert(value)
565 except Exception as e:
566 msg = "%s\nError setting param %s.%s to %s\n" % \
567 (e, cls.__name__, name, value)
568 e.args = (msg, )
569 raise
570 cls._values[name] = value
571 # if param value is a SimObject, make it a child too, so that
572 # it gets cloned properly when the class is instantiated
573 if isSimObjectOrVector(value) and not value.has_parent():
574 cls._add_cls_child(name, value)
575 # update human-readable values of the param if it has a literal
576 # value and is not an object or proxy.
577 if not (isSimObjectOrVector(value) or\
578 isinstance(value, m5.proxy.BaseProxy)):
579 cls._hr_values[name] = hr_value
580
581 def _add_cls_child(cls, name, child):
582 # It's a little funky to have a class as a parent, but these
583 # objects should never be instantiated (only cloned, which
584 # clears the parent pointer), and this makes it clear that the
585 # object is not an orphan and can provide better error
586 # messages.
587 child.set_parent(cls, name)
588 if not isNullPointer(child):
589 cls._children[name] = child
590
591 def _new_port(cls, name, port):
592 # each port should be uniquely assigned to one variable
593 assert(not hasattr(port, 'name'))
594 port.name = name
595 cls._ports[name] = port
596
597 # same as _get_port_ref, effectively, but for classes
598 def _cls_get_port_ref(cls, attr):
599 # Return reference that can be assigned to another port
600 # via __setattr__. There is only ever one reference
601 # object per port, but we create them lazily here.
602 ref = cls._port_refs.get(attr)
603 if not ref:
604 ref = cls._ports[attr].makeRef(cls)
605 cls._port_refs[attr] = ref
606 return ref
607
608 # Set attribute (called on foo.attr = value when foo is an
609 # instance of class cls).
610 def __setattr__(cls, attr, value):
611 # normal processing for private attributes
612 if public_value(attr, value):
613 type.__setattr__(cls, attr, value)
614 return
615
616 if attr in cls.keywords:
617 cls._set_keyword(attr, value, cls.keywords[attr])
618 return
619
620 if attr in cls._ports:
621 cls._cls_get_port_ref(attr).connect(value)
622 return
623
624 if isSimObjectOrSequence(value) and cls._instantiated:
625 raise RuntimeError(
626 "cannot set SimObject parameter '%s' after\n" \
627 " class %s has been instantiated or subclassed" \
628 % (attr, cls.__name__))
629
630 # check for param
631 param = cls._params.get(attr)
632 if param:
633 cls._set_param(attr, value, param)
634 return
635
636 if isSimObjectOrSequence(value):
637 # If RHS is a SimObject, it's an implicit child assignment.
638 cls._add_cls_child(attr, coerceSimObjectOrVector(value))
639 return
640
641 # no valid assignment... raise exception
642 raise AttributeError(
643 "Class %s has no parameter \'%s\'" % (cls.__name__, attr))
644
645 def __getattr__(cls, attr):
646 if attr == 'cxx_class_path':
647 return cls.cxx_class.split('::')
648
649 if attr == 'cxx_class_name':
650 return cls.cxx_class_path[-1]
651
652 if attr == 'cxx_namespaces':
653 return cls.cxx_class_path[:-1]
654
655 if attr in cls._values:
656 return cls._values[attr]
657
658 if attr in cls._children:
659 return cls._children[attr]
660
661 raise AttributeError(
662 "object '%s' has no attribute '%s'" % (cls.__name__, attr))
663
664 def __str__(cls):
665 return cls.__name__
666
667 # See ParamValue.cxx_predecls for description.
668 def cxx_predecls(cls, code):
669 code('#include "params/$cls.hh"')
670
671 def pybind_predecls(cls, code):
672 code('#include "${{cls.cxx_header}}"')
673
674 def pybind_decl(cls, code):
675 class_path = cls.cxx_class.split('::')
676 namespaces, classname = class_path[:-1], class_path[-1]
677 py_class_name = '_COLONS_'.join(class_path) if namespaces else \
678 classname;
679
680 # The 'local' attribute restricts us to the params declared in
681 # the object itself, not including inherited params (which
682 # will also be inherited from the base class's param struct
683 # here). Sort the params based on their key
684 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
685 ports = cls._ports.local
686
687 code('''#include "pybind11/pybind11.h"
688#include "pybind11/stl.h"
689
690#include "params/$cls.hh"
691#include "python/pybind11/core.hh"
692#include "sim/init.hh"
693#include "sim/sim_object.hh"
694
695#include "${{cls.cxx_header}}"
696
697''')
698
699 for param in params:
700 param.pybind_predecls(code)
701
702 code('''namespace py = pybind11;
703
704static void
705module_init(py::module &m_internal)
706{
707 py::module m = m_internal.def_submodule("param_${cls}");
708''')
709 code.indent()
710 if cls._base:
711 code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \
712 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \
713 'm, "${cls}Params")')
714 else:
715 code('py::class_<${cls}Params, ' \
716 'std::unique_ptr<${cls}Params, py::nodelete>>(' \
717 'm, "${cls}Params")')
718
719 code.indent()
720 if not hasattr(cls, 'abstract') or not cls.abstract:
721 code('.def(py::init<>())')
722 code('.def("create", &${cls}Params::create)')
723
724 param_exports = cls.cxx_param_exports + [
725 PyBindProperty(k)
726 for k, v in sorted(cls._params.local.items())
727 ] + [
728 PyBindProperty("port_%s_connection_count" % port.name)
729 for port in ports.values()
730 ]
731 for exp in param_exports:
732 exp.export(code, "%sParams" % cls)
733
734 code(';')
735 code()
736 code.dedent()
737
738 bases = []
739 if 'cxx_base' in cls._value_dict:
740 # If the c++ base class implied by python inheritance was
741 # overridden, use that value.
742 if cls.cxx_base:
743 bases.append(cls.cxx_base)
744 elif cls._base:
745 # If not and if there was a SimObject base, use its c++ class
746 # as this class' base.
747 bases.append(cls._base.cxx_class)
748 # Add in any extra bases that were requested.
749 bases.extend(cls.cxx_extra_bases)
750
751 if bases:
752 base_str = ", ".join(bases)
753 code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \
754 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
755 'm, "${py_class_name}")')
756 else:
757 code('py::class_<${{cls.cxx_class}}, ' \
758 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
759 'm, "${py_class_name}")')
760 code.indent()
761 for exp in cls.cxx_exports:
762 exp.export(code, cls.cxx_class)
763 code(';')
764 code.dedent()
765 code()
766 code.dedent()
767 code('}')
768 code()
769 code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
770 cls, cls._base.type if cls._base else "")
771
772
773 # Generate the C++ declaration (.hh file) for this SimObject's
774 # param struct. Called from src/SConscript.
775 def cxx_param_decl(cls, code):
776 # The 'local' attribute restricts us to the params declared in
777 # the object itself, not including inherited params (which
778 # will also be inherited from the base class's param struct
779 # here). Sort the params based on their key
780 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
781 ports = cls._ports.local
782 try:
783 ptypes = [p.ptype for p in params]
784 except:
785 print(cls, p, p.ptype_str)
786 print(params)
787 raise
788
789 class_path = cls._value_dict['cxx_class'].split('::')
790
791 code('''\
792#ifndef __PARAMS__${cls}__
793#define __PARAMS__${cls}__
794
795''')
796
797
798 # The base SimObject has a couple of params that get
799 # automatically set from Python without being declared through
800 # the normal Param mechanism; we slip them in here (needed
801 # predecls now, actual declarations below)
802 if cls == SimObject:
803 code('''#include <string>''')
804
805 # A forward class declaration is sufficient since we are just
806 # declaring a pointer.
807 for ns in class_path[:-1]:
808 code('namespace $ns {')
809 code('class $0;', class_path[-1])
810 for ns in reversed(class_path[:-1]):
811 code('} // namespace $ns')
812 code()
813
814 for param in params:
815 param.cxx_predecls(code)
816 for port in ports.values():
817 port.cxx_predecls(code)
818 code()
819
820 if cls._base:
821 code('#include "params/${{cls._base.type}}.hh"')
822 code()
823
824 for ptype in ptypes:
825 if issubclass(ptype, Enum):
826 code('#include "enums/${{ptype.__name__}}.hh"')
827 code()
828
829 # now generate the actual param struct
830 code("struct ${cls}Params")
831 if cls._base:
832 code(" : public ${{cls._base.type}}Params")
833 code("{")
834 if not hasattr(cls, 'abstract') or not cls.abstract:
835 if 'type' in cls.__dict__:
836 code(" ${{cls.cxx_type}} create();")
837
838 code.indent()
839 if cls == SimObject:
840 code('''
841 SimObjectParams() {}
842 virtual ~SimObjectParams() {}
843
844 std::string name;
845 ''')
846
847 for param in params:
848 param.cxx_decl(code)
849 for port in ports.values():
850 port.cxx_decl(code)
851
852 code.dedent()
853 code('};')
854
855 code()
856 code('#endif // __PARAMS__${cls}__')
857 return code
858
859 # Generate the C++ declaration/definition files for this SimObject's
860 # param struct to allow C++ initialisation
861 def cxx_config_param_file(cls, code, is_header):
862 createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header)
863 return code
864
865# This *temporary* definition is required to support calls from the
866# SimObject class definition to the MetaSimObject methods (in
867# particular _set_param, which gets called for parameters with default
868# values defined on the SimObject class itself). It will get
869# overridden by the permanent definition (which requires that
870# SimObject be defined) lower in this file.
871def isSimObjectOrVector(value):
872 return False
873
874def cxxMethod(*args, **kwargs):
875 """Decorator to export C++ functions to Python"""
876
877 def decorate(func):
878 name = func.__name__
879 override = kwargs.get("override", False)
880 cxx_name = kwargs.get("cxx_name", name)
881
882 args, varargs, keywords, defaults = inspect.getargspec(func)
883 if varargs or keywords:
884 raise ValueError("Wrapped methods must not contain variable " \
885 "arguments")
886
887 # Create tuples of (argument, default)
888 if defaults:
889 args = args[:-len(defaults)] + \
890 list(zip(args[-len(defaults):], defaults))
891 # Don't include self in the argument list to PyBind
892 args = args[1:]
893
894
895 @wraps(func)
896 def cxx_call(self, *args, **kwargs):
897 ccobj = self.getCCObject()
898 return getattr(ccobj, name)(*args, **kwargs)
899
900 @wraps(func)
901 def py_call(self, *args, **kwargs):
902 return func(self, *args, **kwargs)
903
904 f = py_call if override else cxx_call
905 f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args)
906
907 return f
908
909 if len(args) == 0:
910 return decorate
911 elif len(args) == 1 and len(kwargs) == 0:
912 return decorate(*args)
913 else:
914 raise TypeError("One argument and no kwargs, or only kwargs expected")
915
916# This class holds information about each simobject parameter
917# that should be displayed on the command line for use in the
918# configuration system.
919class ParamInfo(object):
920 def __init__(self, type, desc, type_str, example, default_val, access_str):
921 self.type = type
922 self.desc = desc
923 self.type_str = type_str
924 self.example_str = example
925 self.default_val = default_val
926 # The string representation used to access this param through python.
927 # The method to access this parameter presented on the command line may
928 # be different, so this needs to be stored for later use.
929 self.access_str = access_str
930 self.created = True
931
932 # Make it so we can only set attributes at initialization time
933 # and effectively make this a const object.
934 def __setattr__(self, name, value):
935 if not "created" in self.__dict__:
936 self.__dict__[name] = value
937
938class SimObjectCliWrapperException(Exception):
939 def __init__(self, message):
940 super(Exception, self).__init__(message)
941
942class SimObjectCliWrapper(object):
943 """
944 Wrapper class to restrict operations that may be done
945 from the command line on SimObjects.
946
947 Only parameters may be set, and only children may be accessed.
948
949 Slicing allows for multiple simultaneous assignment of items in
950 one statement.
951 """
952
953 def __init__(self, sim_objects):
954 self.__dict__['_sim_objects'] = list(sim_objects)
955
956 def __getattr__(self, key):
957 return SimObjectCliWrapper(sim_object._children[key]
958 for sim_object in self._sim_objects)
959
960 def __setattr__(self, key, val):
961 for sim_object in self._sim_objects:
962 if key in sim_object._params:
963 if sim_object._params[key].isCmdLineSettable():
964 setattr(sim_object, key, val)
965 else:
966 raise SimObjectCliWrapperException(
967 'tried to set or unsettable' \
968 'object parameter: ' + key)
969 else:
970 raise SimObjectCliWrapperException(
971 'tried to set or access non-existent' \
972 'object parameter: ' + key)
973
974 def __getitem__(self, idx):
975 """
976 Extends the list() semantics to also allow tuples,
977 for example object[1, 3] selects items 1 and 3.
978 """
979 out = []
980 if isinstance(idx, tuple):
981 for t in idx:
982 out.extend(self[t]._sim_objects)
983 else:
984 if isinstance(idx, int):
985 _range = range(idx, idx + 1)
986 elif not isinstance(idx, slice):
987 raise SimObjectCliWrapperException( \
988 'invalid index type: ' + repr(idx))
989 for sim_object in self._sim_objects:
990 if isinstance(idx, slice):
991 _range = range(*idx.indices(len(sim_object)))
992 out.extend(sim_object[i] for i in _range)
993 return SimObjectCliWrapper(out)
994
995# The SimObject class is the root of the special hierarchy. Most of
996# the code in this class deals with the configuration hierarchy itself
997# (parent/child node relationships).
998class SimObject(object):
999 # Specify metaclass. Any class inheriting from SimObject will
1000 # get this metaclass.
1001 __metaclass__ = MetaSimObject
1002 type = 'SimObject'
1003 abstract = True
1004
1005 cxx_header = "sim/sim_object.hh"
1006 cxx_extra_bases = [ "Drainable", "Serializable" ]
1007 eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
1008
1009 cxx_exports = [
1010 PyBindMethod("init"),
1011 PyBindMethod("initState"),
1012 PyBindMethod("memInvalidate"),
1013 PyBindMethod("memWriteback"),
1014 PyBindMethod("regStats"),
1015 PyBindMethod("resetStats"),
1016 PyBindMethod("regProbePoints"),
1017 PyBindMethod("regProbeListeners"),
1018 PyBindMethod("startup"),
1019 ]
1020
1021 cxx_param_exports = [
1022 PyBindProperty("name"),
1023 ]
1024
1025 @cxxMethod
1026 def loadState(self, cp):
1027 """Load SimObject state from a checkpoint"""
1028 pass
1029
1030 # Returns a dict of all the option strings that can be
1031 # generated as command line options for this simobject instance
1032 # by tracing all reachable params in the top level instance and
1033 # any children it contains.
1034 def enumerateParams(self, flags_dict = {},
1035 cmd_line_str = "", access_str = ""):
1036 if hasattr(self, "_paramEnumed"):
1037 print("Cycle detected enumerating params")
1038 else:
1039 self._paramEnumed = True
1040 # Scan the children first to pick up all the objects in this SimObj
1041 for keys in self._children:
1042 child = self._children[keys]
1043 next_cmdline_str = cmd_line_str + keys
1044 next_access_str = access_str + keys
1045 if not isSimObjectVector(child):
1046 next_cmdline_str = next_cmdline_str + "."
1047 next_access_str = next_access_str + "."
1048 flags_dict = child.enumerateParams(flags_dict,
1049 next_cmdline_str,
1050 next_access_str)
1051
1052 # Go through the simple params in the simobject in this level
1053 # of the simobject hierarchy and save information about the
1054 # parameter to be used for generating and processing command line
1055 # options to the simulator to set these parameters.
1056 for keys,values in self._params.items():
1057 if values.isCmdLineSettable():
1058 type_str = ''
1059 ex_str = values.example_str()
1060 ptype = None
1061 if isinstance(values, VectorParamDesc):
1062 type_str = 'Vector_%s' % values.ptype_str
1063 ptype = values
1064 else:
1065 type_str = '%s' % values.ptype_str
1066 ptype = values.ptype
1067
1068 if keys in self._hr_values\
1069 and keys in self._values\
1070 and not isinstance(self._values[keys],
1071 m5.proxy.BaseProxy):
1072 cmd_str = cmd_line_str + keys
1073 acc_str = access_str + keys
1074 flags_dict[cmd_str] = ParamInfo(ptype,
1075 self._params[keys].desc, type_str, ex_str,
1076 values.pretty_print(self._hr_values[keys]),
1077 acc_str)
1078 elif not keys in self._hr_values\
1079 and not keys in self._values:
1080 # Empty param
1081 cmd_str = cmd_line_str + keys
1082 acc_str = access_str + keys
1083 flags_dict[cmd_str] = ParamInfo(ptype,
1084 self._params[keys].desc,
1085 type_str, ex_str, '', acc_str)
1086
1087 return flags_dict
1088
1089 # Initialize new instance. For objects with SimObject-valued
1090 # children, we need to recursively clone the classes represented
1091 # by those param values as well in a consistent "deep copy"-style
1092 # fashion. That is, we want to make sure that each instance is
1093 # cloned only once, and that if there are multiple references to
1094 # the same original object, we end up with the corresponding
1095 # cloned references all pointing to the same cloned instance.
1096 def __init__(self, **kwargs):
1097 ancestor = kwargs.get('_ancestor')
1098 memo_dict = kwargs.get('_memo')
1099 if memo_dict is None:
1100 # prepare to memoize any recursively instantiated objects
1101 memo_dict = {}
1102 elif ancestor:
1103 # memoize me now to avoid problems with recursive calls
1104 memo_dict[ancestor] = self
1105
1106 if not ancestor:
1107 ancestor = self.__class__
1108 ancestor._instantiated = True
1109
1110 # initialize required attributes
1111 self._parent = None
1112 self._name = None
1113 self._ccObject = None # pointer to C++ object
1114 self._ccParams = None
1115 self._instantiated = False # really "cloned"
1116
1117 # Clone children specified at class level. No need for a
1118 # multidict here since we will be cloning everything.
1119 # Do children before parameter values so that children that
1120 # are also param values get cloned properly.
1121 self._children = {}
1122 for key,val in ancestor._children.items():
1123 self.add_child(key, val(_memo=memo_dict))
1124
1125 # Inherit parameter values from class using multidict so
1126 # individual value settings can be overridden but we still
1127 # inherit late changes to non-overridden class values.
1128 self._values = multidict(ancestor._values)
1129 self._hr_values = multidict(ancestor._hr_values)
1130 # clone SimObject-valued parameters
1131 for key,val in ancestor._values.items():
1132 val = tryAsSimObjectOrVector(val)
1133 if val is not None:
1134 self._values[key] = val(_memo=memo_dict)
1135
1136 # clone port references. no need to use a multidict here
1137 # since we will be creating new references for all ports.
1138 self._port_refs = {}
1139 for key,val in ancestor._port_refs.items():
1140 self._port_refs[key] = val.clone(self, memo_dict)
1141 # apply attribute assignments from keyword args, if any
1142 for key,val in kwargs.items():
1143 setattr(self, key, val)
1144
1145 # "Clone" the current instance by creating another instance of
1146 # this instance's class, but that inherits its parameter values
1147 # and port mappings from the current instance. If we're in a
1148 # "deep copy" recursive clone, check the _memo dict to see if
1149 # we've already cloned this instance.
1150 def __call__(self, **kwargs):
1151 memo_dict = kwargs.get('_memo')
1152 if memo_dict is None:
1153 # no memo_dict: must be top-level clone operation.
1154 # this is only allowed at the root of a hierarchy
1155 if self._parent:
1156 raise RuntimeError("attempt to clone object %s " \
1157 "not at the root of a tree (parent = %s)" \
1158 % (self, self._parent))
1159 # create a new dict and use that.
1160 memo_dict = {}
1161 kwargs['_memo'] = memo_dict
1162 elif self in memo_dict:
1163 # clone already done & memoized
1164 return memo_dict[self]
1165 return self.__class__(_ancestor = self, **kwargs)
1166
1167 def _get_port_ref(self, attr):
1168 # Return reference that can be assigned to another port
1169 # via __setattr__. There is only ever one reference
1170 # object per port, but we create them lazily here.
1171 ref = self._port_refs.get(attr)
1172 if ref == None:
1173 ref = self._ports[attr].makeRef(self)
1174 self._port_refs[attr] = ref
1175 return ref
1176
1177 def __getattr__(self, attr):
1178 if attr in self._ports:
1179 return self._get_port_ref(attr)
1180
1181 if attr in self._values:
1182 return self._values[attr]
1183
1184 if attr in self._children:
1185 return self._children[attr]
1186
1187 # If the attribute exists on the C++ object, transparently
1188 # forward the reference there. This is typically used for
1189 # methods exported to Python (e.g., init(), and startup())
1190 if self._ccObject and hasattr(self._ccObject, attr):
1191 return getattr(self._ccObject, attr)
1192
1193 err_string = "object '%s' has no attribute '%s'" \
1194 % (self.__class__.__name__, attr)
1195
1196 if not self._ccObject:
1197 err_string += "\n (C++ object is not yet constructed," \
1198 " so wrapped C++ methods are unavailable.)"
1199
1200 raise AttributeError(err_string)
1201
1202 # Set attribute (called on foo.attr = value when foo is an
1203 # instance of class cls).
1204 def __setattr__(self, attr, value):
1205 # normal processing for private attributes
1206 if attr.startswith('_'):
1207 object.__setattr__(self, attr, value)
1208 return
1209
1210 if attr in self._ports:
1211 # set up port connection
1212 self._get_port_ref(attr).connect(value)
1213 return
1214
1215 param = self._params.get(attr)
1216 if param:
1217 try:
1218 hr_value = value
1219 value = param.convert(value)
1220 except Exception as e:
1221 msg = "%s\nError setting param %s.%s to %s\n" % \
1222 (e, self.__class__.__name__, attr, value)
1223 e.args = (msg, )
1224 raise
1225 self._values[attr] = value
1226 # implicitly parent unparented objects assigned as params
1227 if isSimObjectOrVector(value) and not value.has_parent():
1228 self.add_child(attr, value)
1229 # set the human-readable value dict if this is a param
1230 # with a literal value and is not being set as an object
1231 # or proxy.
1232 if not (isSimObjectOrVector(value) or\
1233 isinstance(value, m5.proxy.BaseProxy)):
1234 self._hr_values[attr] = hr_value
1235
1236 return
1237
1238 # if RHS is a SimObject, it's an implicit child assignment
1239 if isSimObjectOrSequence(value):
1240 self.add_child(attr, value)
1241 return
1242
1243 # no valid assignment... raise exception
1244 raise AttributeError("Class %s has no parameter %s" \
1245 % (self.__class__.__name__, attr))
1246
1247
1248 # this hack allows tacking a '[0]' onto parameters that may or may
1249 # not be vectors, and always getting the first element (e.g. cpus)
1250 def __getitem__(self, key):
1251 if key == 0:
1252 return self
1253 raise IndexError("Non-zero index '%s' to SimObject" % key)
1254
1255 # this hack allows us to iterate over a SimObject that may
1256 # not be a vector, so we can call a loop over it and get just one
1257 # element.
1258 def __len__(self):
1259 return 1
1260
1261 # Also implemented by SimObjectVector
1262 def clear_parent(self, old_parent):
1263 assert self._parent is old_parent
1264 self._parent = None
1265
1266 # Also implemented by SimObjectVector
1267 def set_parent(self, parent, name):
1268 self._parent = parent
1269 self._name = name
1270
1271 # Return parent object of this SimObject, not implemented by
1272 # SimObjectVector because the elements in a SimObjectVector may not share
1273 # the same parent
1274 def get_parent(self):
1275 return self._parent
1276
1277 # Also implemented by SimObjectVector
1278 def get_name(self):
1279 return self._name
1280
1281 # Also implemented by SimObjectVector
1282 def has_parent(self):
1283 return self._parent is not None
1284
1285 # clear out child with given name. This code is not likely to be exercised.
1286 # See comment in add_child.
1287 def clear_child(self, name):
1288 child = self._children[name]
1289 child.clear_parent(self)
1290 del self._children[name]
1291
1292 # Add a new child to this object.
1293 def add_child(self, name, child):
1294 child = coerceSimObjectOrVector(child)
1295 if child.has_parent():
1296 warn("add_child('%s'): child '%s' already has parent", name,
1297 child.get_name())
1298 if name in self._children:
1299 # This code path had an undiscovered bug that would make it fail
1300 # at runtime. It had been here for a long time and was only
1301 # exposed by a buggy script. Changes here will probably not be
1302 # exercised without specialized testing.
1303 self.clear_child(name)
1304 child.set_parent(self, name)
1305 if not isNullPointer(child):
1306 self._children[name] = child
1307
1308 # Take SimObject-valued parameters that haven't been explicitly
1309 # assigned as children and make them children of the object that
1310 # they were assigned to as a parameter value. This guarantees
1311 # that when we instantiate all the parameter objects we're still
1312 # inside the configuration hierarchy.
1313 def adoptOrphanParams(self):
1314 for key,val in self._values.items():
1315 if not isSimObjectVector(val) and isSimObjectSequence(val):
1316 # need to convert raw SimObject sequences to
1317 # SimObjectVector class so we can call has_parent()
1318 val = SimObjectVector(val)
1319 self._values[key] = val
1320 if isSimObjectOrVector(val) and not val.has_parent():
1321 warn("%s adopting orphan SimObject param '%s'", self, key)
1322 self.add_child(key, val)
1323
1324 def path(self):
1325 if not self._parent:
1326 return '<orphan %s>' % self.__class__
1327 elif isinstance(self._parent, MetaSimObject):
1328 return str(self.__class__)
1329
1330 ppath = self._parent.path()
1331 if ppath == 'root':
1332 return self._name
1333 return ppath + "." + self._name
1334
1335 def __str__(self):
1336 return self.path()
1337
1338 def config_value(self):
1339 return self.path()
1340
1341 def ini_str(self):
1342 return self.path()
1343
1344 def find_any(self, ptype):
1345 if isinstance(self, ptype):
1346 return self, True
1347
1348 found_obj = None
1349 for child in self._children.values():
1350 visited = False
1351 if hasattr(child, '_visited'):
1352 visited = getattr(child, '_visited')
1353
1354 if isinstance(child, ptype) and not visited:
1355 if found_obj != None and child != found_obj:
1356 raise AttributeError(
1357 'parent.any matched more than one: %s %s' % \
1358 (found_obj.path, child.path))
1359 found_obj = child
1360 # search param space
1361 for pname,pdesc in self._params.items():
1362 if issubclass(pdesc.ptype, ptype):
1363 match_obj = self._values[pname]
1364 if found_obj != None and found_obj != match_obj:
1365 raise AttributeError(
1366 'parent.any matched more than one: %s and %s' % \
1367 (found_obj.path, match_obj.path))
1368 found_obj = match_obj
1369 return found_obj, found_obj != None
1370
1371 def find_all(self, ptype):
1372 all = {}
1373 # search children
1374 for child in self._children.values():
1375 # a child could be a list, so ensure we visit each item
1376 if isinstance(child, list):
1377 children = child
1378 else:
1379 children = [child]
1380
1381 for child in children:
1382 if isinstance(child, ptype) and not isproxy(child) and \
1383 not isNullPointer(child):
1384 all[child] = True
1385 if isSimObject(child):
1386 # also add results from the child itself
1387 child_all, done = child.find_all(ptype)
1388 all.update(dict(zip(child_all, [done] * len(child_all))))
1389 # search param space
1390 for pname,pdesc in self._params.items():
1391 if issubclass(pdesc.ptype, ptype):
1392 match_obj = self._values[pname]
1393 if not isproxy(match_obj) and not isNullPointer(match_obj):
1394 all[match_obj] = True
1395 # Also make sure to sort the keys based on the objects' path to
1396 # ensure that the order is the same on all hosts
1397 return sorted(all.keys(), key = lambda o: o.path()), True
1398
1399 def unproxy(self, base):
1400 return self
1401
1402 def unproxyParams(self):
1403 for param in self._params.keys():
1404 value = self._values.get(param)
1405 if value != None and isproxy(value):
1406 try:
1407 value = value.unproxy(self)
1408 except:
1409 print("Error in unproxying param '%s' of %s" %
1410 (param, self.path()))
1411 raise
1412 setattr(self, param, value)
1413
1414 # Unproxy ports in sorted order so that 'append' operations on
1415 # vector ports are done in a deterministic fashion.
1416 port_names = list(self._ports.keys())
1417 port_names.sort()
1418 for port_name in port_names:
1419 port = self._port_refs.get(port_name)
1420 if port != None:
1421 port.unproxy(self)
1422
1423 def print_ini(self, ini_file):
1424 print('[' + self.path() + ']', file=ini_file) # .ini section header
1425
1426 instanceDict[self.path()] = self
1427
1428 if hasattr(self, 'type'):
1429 print('type=%s' % self.type, file=ini_file)
1430
1431 if len(self._children.keys()):
1432 print('children=%s' %
1433 ' '.join(self._children[n].get_name()
1434 for n in sorted(self._children.keys())),
1435 file=ini_file)
1436
1437 for param in sorted(self._params.keys()):
1438 value = self._values.get(param)
1439 if value != None:
1440 print('%s=%s' % (param, self._values[param].ini_str()),
1441 file=ini_file)
1442
1443 for port_name in sorted(self._ports.keys()):
1444 port = self._port_refs.get(port_name, None)
1445 if port != None:
1446 print('%s=%s' % (port_name, port.ini_str()), file=ini_file)
1447
1448 print(file=ini_file) # blank line between objects
1449
1450 # generate a tree of dictionaries expressing all the parameters in the
1451 # instantiated system for use by scripts that want to do power, thermal
1452 # visualization, and other similar tasks
1453 def get_config_as_dict(self):
1454 d = attrdict()
1455 if hasattr(self, 'type'):
1456 d.type = self.type
1457 if hasattr(self, 'cxx_class'):
1458 d.cxx_class = self.cxx_class
1459 # Add the name and path of this object to be able to link to
1460 # the stats
1461 d.name = self.get_name()
1462 d.path = self.path()
1463
1464 for param in sorted(self._params.keys()):
1465 value = self._values.get(param)
1466 if value != None:
1467 d[param] = value.config_value()
1468
1469 for n in sorted(self._children.keys()):
1470 child = self._children[n]
1471 # Use the name of the attribute (and not get_name()) as
1472 # the key in the JSON dictionary to capture the hierarchy
1473 # in the Python code that assembled this system
1474 d[n] = child.get_config_as_dict()
1475
1476 for port_name in sorted(self._ports.keys()):
1477 port = self._port_refs.get(port_name, None)
1478 if port != None:
1479 # Represent each port with a dictionary containing the
1480 # prominent attributes
1481 d[port_name] = port.get_config_as_dict()
1482
1483 return d
1484
1485 def getCCParams(self):
1486 if self._ccParams:
1487 return self._ccParams
1488
1489 cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type)
1490 cc_params = cc_params_struct()
1491 cc_params.name = str(self)
1492
1493 param_names = list(self._params.keys())
1494 param_names.sort()
1495 for param in param_names:
1496 value = self._values.get(param)
1497 if value is None:
1498 fatal("%s.%s without default or user set value",
1499 self.path(), param)
1500
1501 value = value.getValue()
1502 if isinstance(self._params[param], VectorParamDesc):
1503 assert isinstance(value, list)
1504 vec = getattr(cc_params, param)
1505 assert not len(vec)
1506 # Some types are exposed as opaque types. They support
1507 # the append operation unlike the automatically
1508 # wrapped types.
1509 if isinstance(vec, list):
1510 setattr(cc_params, param, list(value))
1511 else:
1512 for v in value:
1513 getattr(cc_params, param).append(v)
1514 else:
1515 setattr(cc_params, param, value)
1516
1517 port_names = list(self._ports.keys())
1518 port_names.sort()
1519 for port_name in port_names:
1520 port = self._port_refs.get(port_name, None)
1521 if port != None:
1522 port_count = len(port)
1523 else:
1524 port_count = 0
1525 setattr(cc_params, 'port_' + port_name + '_connection_count',
1526 port_count)
1527 self._ccParams = cc_params
1528 return self._ccParams
1529
1530 # Get C++ object corresponding to this object, calling C++ if
1531 # necessary to construct it. Does *not* recursively create
1532 # children.
1533 def getCCObject(self):
1534 if not self._ccObject:
1535 # Make sure this object is in the configuration hierarchy
1536 if not self._parent and not isRoot(self):
1537 raise RuntimeError("Attempt to instantiate orphan node")
1538 # Cycles in the configuration hierarchy are not supported. This
1539 # will catch the resulting recursion and stop.
1540 self._ccObject = -1
1541 if not self.abstract:
1542 params = self.getCCParams()
1543 self._ccObject = params.create()
1544 elif self._ccObject == -1:
1545 raise RuntimeError("%s: Cycle found in configuration hierarchy." \
1546 % self.path())
1547 return self._ccObject
1548
1549 def descendants(self):
1550 yield self
1551 # The order of the dict is implementation dependent, so sort
1552 # it based on the key (name) to ensure the order is the same
1553 # on all hosts
1554 for (name, child) in sorted(self._children.items()):
1555 for obj in child.descendants():
1556 yield obj
1557
1558 # Call C++ to create C++ object corresponding to this object
1559 def createCCObject(self):
1560 self.getCCParams()
1561 self.getCCObject() # force creation
1562
1563 def getValue(self):
1564 return self.getCCObject()
1565
1566 # Create C++ port connections corresponding to the connections in
1567 # _port_refs
1568 def connectPorts(self):
1569 # Sort the ports based on their attribute name to ensure the
1570 # order is the same on all hosts
1571 for (attr, portRef) in sorted(self._port_refs.items()):
1572 portRef.ccConnect()
1573
1574 # Default function for generating the device structure.
1575 # Can be overloaded by the inheriting class
1576 def generateDeviceTree(self, state):
1577 return # return without yielding anything
1578 yield # make this function a (null) generator
1579
1580 def recurseDeviceTree(self, state):
1581 for child in self._children.values():
1582 for item in child: # For looping over SimObjectVectors
1583 for dt in item.generateDeviceTree(state):
1584 yield dt
1585
1586 # On a separate method otherwise certain buggy Python versions
1587 # would fail with: SyntaxError: unqualified exec is not allowed
1588 # in function 'apply_config'
1589 def _apply_config_get_dict(self):
1590 return {
1591 child_name: SimObjectCliWrapper(
1592 iter(self._children[child_name]))
1593 for child_name in self._children
1594 }
1595
1596 def apply_config(self, params):
1597 """
1598 exec a list of Python code strings contained in params.
1599
1600 The only exposed globals to those strings are the child
1601 SimObjects of this node.
1602
1603 This function is intended to allow users to modify SimObject
1604 parameters from the command line with Python statements.
1605 """
1606 d = self._apply_config_get_dict()
1607 for param in params:
1608 exec(param, d)
1609
1610# Function to provide to C++ so it can look up instances based on paths
1611def resolveSimObject(name):
1612 obj = instanceDict[name]
1613 return obj.getCCObject()
1614
1615def isSimObject(value):
1616 return isinstance(value, SimObject)
1617
1618def isSimObjectClass(value):
1619 return issubclass(value, SimObject)
1620
1621def isSimObjectVector(value):
1622 return isinstance(value, SimObjectVector)
1623
1624def isSimObjectSequence(value):
1625 if not isinstance(value, (list, tuple)) or len(value) == 0:
1626 return False
1627
1628 for val in value:
1629 if not isNullPointer(val) and not isSimObject(val):
1630 return False
1631
1632 return True
1633
1634def isSimObjectOrSequence(value):
1635 return isSimObject(value) or isSimObjectSequence(value)
1636
1637def isRoot(obj):
1638 from m5.objects import Root
1639 return obj and obj is Root.getInstance()
1640
1641def isSimObjectOrVector(value):
1642 return isSimObject(value) or isSimObjectVector(value)
1643
1644def tryAsSimObjectOrVector(value):
1645 if isSimObjectOrVector(value):
1646 return value
1647 if isSimObjectSequence(value):
1648 return SimObjectVector(value)
1649 return None
1650
1651def coerceSimObjectOrVector(value):
1652 value = tryAsSimObjectOrVector(value)
1653 if value is None:
1654 raise TypeError("SimObject or SimObjectVector expected")
1655 return value
1656
1657baseClasses = allClasses.copy()
1658baseInstances = instanceDict.copy()
1659
1660def clear():
1661 global allClasses, instanceDict, noCxxHeader
1662
1663 allClasses = baseClasses.copy()
1664 instanceDict = baseInstances.copy()
1665 noCxxHeader = False
1666
1667# __all__ defines the list of symbols that get exported when
1668# 'from config import *' is invoked. Try to keep this reasonably
1669# short to avoid polluting other namespaces.
1670__all__ = [
1671 'SimObject',
1672 'cxxMethod',
1673 'PyBindMethod',
1674 'PyBindProperty',
1675]
48
49import sys
50from types import FunctionType, MethodType, ModuleType
51from functools import wraps
52import inspect
53
54import m5
55from m5.util import *
56from m5.util.pybind import *
57# Use the pyfdt and not the helper class, because the fdthelper
58# relies on the SimObject definition
59from m5.ext.pyfdt import pyfdt
60
61# Have to import params up top since Param is referenced on initial
62# load (when SimObject class references Param to create a class
63# variable, the 'name' param)...
64from m5.params import *
65# There are a few things we need that aren't in params.__all__ since
66# normal users don't need them
67from m5.params import ParamDesc, VectorParamDesc, \
68 isNullPointer, SimObjectVector, Port
69
70from m5.proxy import *
71from m5.proxy import isproxy
72
73#####################################################################
74#
75# M5 Python Configuration Utility
76#
77# The basic idea is to write simple Python programs that build Python
78# objects corresponding to M5 SimObjects for the desired simulation
79# configuration. For now, the Python emits a .ini file that can be
80# parsed by M5. In the future, some tighter integration between M5
81# and the Python interpreter may allow bypassing the .ini file.
82#
83# Each SimObject class in M5 is represented by a Python class with the
84# same name. The Python inheritance tree mirrors the M5 C++ tree
85# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
86# SimObjects inherit from a single SimObject base class). To specify
87# an instance of an M5 SimObject in a configuration, the user simply
88# instantiates the corresponding Python object. The parameters for
89# that SimObject are given by assigning to attributes of the Python
90# object, either using keyword assignment in the constructor or in
91# separate assignment statements. For example:
92#
93# cache = BaseCache(size='64KB')
94# cache.hit_latency = 3
95# cache.assoc = 8
96#
97# The magic lies in the mapping of the Python attributes for SimObject
98# classes to the actual SimObject parameter specifications. This
99# allows parameter validity checking in the Python code. Continuing
100# the example above, the statements "cache.blurfl=3" or
101# "cache.assoc='hello'" would both result in runtime errors in Python,
102# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
103# parameter requires an integer, respectively. This magic is done
104# primarily by overriding the special __setattr__ method that controls
105# assignment to object attributes.
106#
107# Once a set of Python objects have been instantiated in a hierarchy,
108# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
109# will generate a .ini file.
110#
111#####################################################################
112
113# list of all SimObject classes
114allClasses = {}
115
116# dict to look up SimObjects based on path
117instanceDict = {}
118
119# Did any of the SimObjects lack a header file?
120noCxxHeader = False
121
122def public_value(key, value):
123 return key.startswith('_') or \
124 isinstance(value, (FunctionType, MethodType, ModuleType,
125 classmethod, type))
126
127def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header):
128 entry_class = 'CxxConfigDirectoryEntry_%s' % name
129 param_class = '%sCxxConfigParams' % name
130
131 code('#include "params/%s.hh"' % name)
132
133 if not is_header:
134 for param in simobj._params.values():
135 if isSimObjectClass(param.ptype):
136 code('#include "%s"' % param.ptype._value_dict['cxx_header'])
137 code('#include "params/%s.hh"' % param.ptype.__name__)
138 else:
139 param.ptype.cxx_ini_predecls(code)
140
141 if is_header:
142 member_prefix = ''
143 end_of_decl = ';'
144 code('#include "sim/cxx_config.hh"')
145 code()
146 code('class ${param_class} : public CxxConfigParams,'
147 ' public ${name}Params')
148 code('{')
149 code(' private:')
150 code.indent()
151 code('class DirectoryEntry : public CxxConfigDirectoryEntry')
152 code('{')
153 code(' public:')
154 code.indent()
155 code('DirectoryEntry();');
156 code()
157 code('CxxConfigParams *makeParamsObject() const')
158 code('{ return new ${param_class}; }')
159 code.dedent()
160 code('};')
161 code()
162 code.dedent()
163 code(' public:')
164 code.indent()
165 else:
166 member_prefix = '%s::' % param_class
167 end_of_decl = ''
168 code('#include "%s"' % simobj._value_dict['cxx_header'])
169 code('#include "base/str.hh"')
170 code('#include "cxx_config/${name}.hh"')
171
172 if simobj._ports:
173 code('#include "mem/mem_object.hh"')
174 code('#include "mem/port.hh"')
175
176 code()
177 code('${member_prefix}DirectoryEntry::DirectoryEntry()');
178 code('{')
179
180 def cxx_bool(b):
181 return 'true' if b else 'false'
182
183 code.indent()
184 for param in simobj._params.values():
185 is_vector = isinstance(param, m5.params.VectorParamDesc)
186 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
187
188 code('parameters["%s"] = new ParamDesc("%s", %s, %s);' %
189 (param.name, param.name, cxx_bool(is_vector),
190 cxx_bool(is_simobj)));
191
192 for port in simobj._ports.values():
193 is_vector = isinstance(port, m5.params.VectorPort)
194 is_master = port.role == 'MASTER'
195
196 code('ports["%s"] = new PortDesc("%s", %s, %s);' %
197 (port.name, port.name, cxx_bool(is_vector),
198 cxx_bool(is_master)))
199
200 code.dedent()
201 code('}')
202 code()
203
204 code('bool ${member_prefix}setSimObject(const std::string &name,')
205 code(' SimObject *simObject)${end_of_decl}')
206
207 if not is_header:
208 code('{')
209 code.indent()
210 code('bool ret = true;')
211 code()
212 code('if (false) {')
213 for param in simobj._params.values():
214 is_vector = isinstance(param, m5.params.VectorParamDesc)
215 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
216
217 if is_simobj and not is_vector:
218 code('} else if (name == "${{param.name}}") {')
219 code.indent()
220 code('this->${{param.name}} = '
221 'dynamic_cast<${{param.ptype.cxx_type}}>(simObject);')
222 code('if (simObject && !this->${{param.name}})')
223 code(' ret = false;')
224 code.dedent()
225 code('} else {')
226 code(' ret = false;')
227 code('}')
228 code()
229 code('return ret;')
230 code.dedent()
231 code('}')
232
233 code()
234 code('bool ${member_prefix}setSimObjectVector('
235 'const std::string &name,')
236 code(' const std::vector<SimObject *> &simObjects)${end_of_decl}')
237
238 if not is_header:
239 code('{')
240 code.indent()
241 code('bool ret = true;')
242 code()
243 code('if (false) {')
244 for param in simobj._params.values():
245 is_vector = isinstance(param, m5.params.VectorParamDesc)
246 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
247
248 if is_simobj and is_vector:
249 code('} else if (name == "${{param.name}}") {')
250 code.indent()
251 code('this->${{param.name}}.clear();')
252 code('for (auto i = simObjects.begin(); '
253 'ret && i != simObjects.end(); i ++)')
254 code('{')
255 code.indent()
256 code('${{param.ptype.cxx_type}} object = '
257 'dynamic_cast<${{param.ptype.cxx_type}}>(*i);')
258 code('if (*i && !object)')
259 code(' ret = false;')
260 code('else')
261 code(' this->${{param.name}}.push_back(object);')
262 code.dedent()
263 code('}')
264 code.dedent()
265 code('} else {')
266 code(' ret = false;')
267 code('}')
268 code()
269 code('return ret;')
270 code.dedent()
271 code('}')
272
273 code()
274 code('void ${member_prefix}setName(const std::string &name_)'
275 '${end_of_decl}')
276
277 if not is_header:
278 code('{')
279 code.indent()
280 code('this->name = name_;')
281 code.dedent()
282 code('}')
283
284 if is_header:
285 code('const std::string &${member_prefix}getName()')
286 code('{ return this->name; }')
287
288 code()
289 code('bool ${member_prefix}setParam(const std::string &name,')
290 code(' const std::string &value, const Flags flags)${end_of_decl}')
291
292 if not is_header:
293 code('{')
294 code.indent()
295 code('bool ret = true;')
296 code()
297 code('if (false) {')
298 for param in simobj._params.values():
299 is_vector = isinstance(param, m5.params.VectorParamDesc)
300 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
301
302 if not is_simobj and not is_vector:
303 code('} else if (name == "${{param.name}}") {')
304 code.indent()
305 param.ptype.cxx_ini_parse(code,
306 'value', 'this->%s' % param.name, 'ret =')
307 code.dedent()
308 code('} else {')
309 code(' ret = false;')
310 code('}')
311 code()
312 code('return ret;')
313 code.dedent()
314 code('}')
315
316 code()
317 code('bool ${member_prefix}setParamVector('
318 'const std::string &name,')
319 code(' const std::vector<std::string> &values,')
320 code(' const Flags flags)${end_of_decl}')
321
322 if not is_header:
323 code('{')
324 code.indent()
325 code('bool ret = true;')
326 code()
327 code('if (false) {')
328 for param in simobj._params.values():
329 is_vector = isinstance(param, m5.params.VectorParamDesc)
330 is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
331
332 if not is_simobj and is_vector:
333 code('} else if (name == "${{param.name}}") {')
334 code.indent()
335 code('${{param.name}}.clear();')
336 code('for (auto i = values.begin(); '
337 'ret && i != values.end(); i ++)')
338 code('{')
339 code.indent()
340 code('${{param.ptype.cxx_type}} elem;')
341 param.ptype.cxx_ini_parse(code,
342 '*i', 'elem', 'ret =')
343 code('if (ret)')
344 code(' this->${{param.name}}.push_back(elem);')
345 code.dedent()
346 code('}')
347 code.dedent()
348 code('} else {')
349 code(' ret = false;')
350 code('}')
351 code()
352 code('return ret;')
353 code.dedent()
354 code('}')
355
356 code()
357 code('bool ${member_prefix}setPortConnectionCount('
358 'const std::string &name,')
359 code(' unsigned int count)${end_of_decl}')
360
361 if not is_header:
362 code('{')
363 code.indent()
364 code('bool ret = true;')
365 code()
366 code('if (false)')
367 code(' ;')
368 for port in simobj._ports.values():
369 code('else if (name == "${{port.name}}")')
370 code(' this->port_${{port.name}}_connection_count = count;')
371 code('else')
372 code(' ret = false;')
373 code()
374 code('return ret;')
375 code.dedent()
376 code('}')
377
378 code()
379 code('SimObject *${member_prefix}simObjectCreate()${end_of_decl}')
380
381 if not is_header:
382 code('{')
383 if hasattr(simobj, 'abstract') and simobj.abstract:
384 code(' return NULL;')
385 else:
386 code(' return this->create();')
387 code('}')
388
389 if is_header:
390 code()
391 code('static CxxConfigDirectoryEntry'
392 ' *${member_prefix}makeDirectoryEntry()')
393 code('{ return new DirectoryEntry; }')
394
395 if is_header:
396 code.dedent()
397 code('};')
398
399# The metaclass for SimObject. This class controls how new classes
400# that derive from SimObject are instantiated, and provides inherited
401# class behavior (just like a class controls how instances of that
402# class are instantiated, and provides inherited instance behavior).
403class MetaSimObject(type):
404 # Attributes that can be set only at initialization time
405 init_keywords = {
406 'abstract' : bool,
407 'cxx_class' : str,
408 'cxx_type' : str,
409 'cxx_header' : str,
410 'type' : str,
411 'cxx_base' : (str, type(None)),
412 'cxx_extra_bases' : list,
413 'cxx_exports' : list,
414 'cxx_param_exports' : list,
415 }
416 # Attributes that can be set any time
417 keywords = { 'check' : FunctionType }
418
419 # __new__ is called before __init__, and is where the statements
420 # in the body of the class definition get loaded into the class's
421 # __dict__. We intercept this to filter out parameter & port assignments
422 # and only allow "private" attributes to be passed to the base
423 # __new__ (starting with underscore).
424 def __new__(mcls, name, bases, dict):
425 assert name not in allClasses, "SimObject %s already present" % name
426
427 # Copy "private" attributes, functions, and classes to the
428 # official dict. Everything else goes in _init_dict to be
429 # filtered in __init__.
430 cls_dict = {}
431 value_dict = {}
432 cxx_exports = []
433 for key,val in dict.items():
434 try:
435 cxx_exports.append(getattr(val, "__pybind"))
436 except AttributeError:
437 pass
438
439 if public_value(key, val):
440 cls_dict[key] = val
441 else:
442 # must be a param/port setting
443 value_dict[key] = val
444 if 'abstract' not in value_dict:
445 value_dict['abstract'] = False
446 if 'cxx_extra_bases' not in value_dict:
447 value_dict['cxx_extra_bases'] = []
448 if 'cxx_exports' not in value_dict:
449 value_dict['cxx_exports'] = cxx_exports
450 else:
451 value_dict['cxx_exports'] += cxx_exports
452 if 'cxx_param_exports' not in value_dict:
453 value_dict['cxx_param_exports'] = []
454 cls_dict['_value_dict'] = value_dict
455 cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
456 if 'type' in value_dict:
457 allClasses[name] = cls
458 return cls
459
460 # subclass initialization
461 def __init__(cls, name, bases, dict):
462 # calls type.__init__()... I think that's a no-op, but leave
463 # it here just in case it's not.
464 super(MetaSimObject, cls).__init__(name, bases, dict)
465
466 # initialize required attributes
467
468 # class-only attributes
469 cls._params = multidict() # param descriptions
470 cls._ports = multidict() # port descriptions
471
472 # class or instance attributes
473 cls._values = multidict() # param values
474 cls._hr_values = multidict() # human readable param values
475 cls._children = multidict() # SimObject children
476 cls._port_refs = multidict() # port ref objects
477 cls._instantiated = False # really instantiated, cloned, or subclassed
478
479 # We don't support multiple inheritance of sim objects. If you want
480 # to, you must fix multidict to deal with it properly. Non sim-objects
481 # are ok, though
482 bTotal = 0
483 for c in bases:
484 if isinstance(c, MetaSimObject):
485 bTotal += 1
486 if bTotal > 1:
487 raise TypeError(
488 "SimObjects do not support multiple inheritance")
489
490 base = bases[0]
491
492 # Set up general inheritance via multidicts. A subclass will
493 # inherit all its settings from the base class. The only time
494 # the following is not true is when we define the SimObject
495 # class itself (in which case the multidicts have no parent).
496 if isinstance(base, MetaSimObject):
497 cls._base = base
498 cls._params.parent = base._params
499 cls._ports.parent = base._ports
500 cls._values.parent = base._values
501 cls._hr_values.parent = base._hr_values
502 cls._children.parent = base._children
503 cls._port_refs.parent = base._port_refs
504 # mark base as having been subclassed
505 base._instantiated = True
506 else:
507 cls._base = None
508
509 # default keyword values
510 if 'type' in cls._value_dict:
511 if 'cxx_class' not in cls._value_dict:
512 cls._value_dict['cxx_class'] = cls._value_dict['type']
513
514 cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class']
515
516 if 'cxx_header' not in cls._value_dict:
517 global noCxxHeader
518 noCxxHeader = True
519 warn("No header file specified for SimObject: %s", name)
520
521 # Now process the _value_dict items. They could be defining
522 # new (or overriding existing) parameters or ports, setting
523 # class keywords (e.g., 'abstract'), or setting parameter
524 # values or port bindings. The first 3 can only be set when
525 # the class is defined, so we handle them here. The others
526 # can be set later too, so just emulate that by calling
527 # setattr().
528 for key,val in cls._value_dict.items():
529 # param descriptions
530 if isinstance(val, ParamDesc):
531 cls._new_param(key, val)
532
533 # port objects
534 elif isinstance(val, Port):
535 cls._new_port(key, val)
536
537 # init-time-only keywords
538 elif key in cls.init_keywords:
539 cls._set_keyword(key, val, cls.init_keywords[key])
540
541 # default: use normal path (ends up in __setattr__)
542 else:
543 setattr(cls, key, val)
544
545 def _set_keyword(cls, keyword, val, kwtype):
546 if not isinstance(val, kwtype):
547 raise TypeError('keyword %s has bad type %s (expecting %s)' % \
548 (keyword, type(val), kwtype))
549 if isinstance(val, FunctionType):
550 val = classmethod(val)
551 type.__setattr__(cls, keyword, val)
552
553 def _new_param(cls, name, pdesc):
554 # each param desc should be uniquely assigned to one variable
555 assert(not hasattr(pdesc, 'name'))
556 pdesc.name = name
557 cls._params[name] = pdesc
558 if hasattr(pdesc, 'default'):
559 cls._set_param(name, pdesc.default, pdesc)
560
561 def _set_param(cls, name, value, param):
562 assert(param.name == name)
563 try:
564 hr_value = value
565 value = param.convert(value)
566 except Exception as e:
567 msg = "%s\nError setting param %s.%s to %s\n" % \
568 (e, cls.__name__, name, value)
569 e.args = (msg, )
570 raise
571 cls._values[name] = value
572 # if param value is a SimObject, make it a child too, so that
573 # it gets cloned properly when the class is instantiated
574 if isSimObjectOrVector(value) and not value.has_parent():
575 cls._add_cls_child(name, value)
576 # update human-readable values of the param if it has a literal
577 # value and is not an object or proxy.
578 if not (isSimObjectOrVector(value) or\
579 isinstance(value, m5.proxy.BaseProxy)):
580 cls._hr_values[name] = hr_value
581
582 def _add_cls_child(cls, name, child):
583 # It's a little funky to have a class as a parent, but these
584 # objects should never be instantiated (only cloned, which
585 # clears the parent pointer), and this makes it clear that the
586 # object is not an orphan and can provide better error
587 # messages.
588 child.set_parent(cls, name)
589 if not isNullPointer(child):
590 cls._children[name] = child
591
592 def _new_port(cls, name, port):
593 # each port should be uniquely assigned to one variable
594 assert(not hasattr(port, 'name'))
595 port.name = name
596 cls._ports[name] = port
597
598 # same as _get_port_ref, effectively, but for classes
599 def _cls_get_port_ref(cls, attr):
600 # Return reference that can be assigned to another port
601 # via __setattr__. There is only ever one reference
602 # object per port, but we create them lazily here.
603 ref = cls._port_refs.get(attr)
604 if not ref:
605 ref = cls._ports[attr].makeRef(cls)
606 cls._port_refs[attr] = ref
607 return ref
608
609 # Set attribute (called on foo.attr = value when foo is an
610 # instance of class cls).
611 def __setattr__(cls, attr, value):
612 # normal processing for private attributes
613 if public_value(attr, value):
614 type.__setattr__(cls, attr, value)
615 return
616
617 if attr in cls.keywords:
618 cls._set_keyword(attr, value, cls.keywords[attr])
619 return
620
621 if attr in cls._ports:
622 cls._cls_get_port_ref(attr).connect(value)
623 return
624
625 if isSimObjectOrSequence(value) and cls._instantiated:
626 raise RuntimeError(
627 "cannot set SimObject parameter '%s' after\n" \
628 " class %s has been instantiated or subclassed" \
629 % (attr, cls.__name__))
630
631 # check for param
632 param = cls._params.get(attr)
633 if param:
634 cls._set_param(attr, value, param)
635 return
636
637 if isSimObjectOrSequence(value):
638 # If RHS is a SimObject, it's an implicit child assignment.
639 cls._add_cls_child(attr, coerceSimObjectOrVector(value))
640 return
641
642 # no valid assignment... raise exception
643 raise AttributeError(
644 "Class %s has no parameter \'%s\'" % (cls.__name__, attr))
645
646 def __getattr__(cls, attr):
647 if attr == 'cxx_class_path':
648 return cls.cxx_class.split('::')
649
650 if attr == 'cxx_class_name':
651 return cls.cxx_class_path[-1]
652
653 if attr == 'cxx_namespaces':
654 return cls.cxx_class_path[:-1]
655
656 if attr in cls._values:
657 return cls._values[attr]
658
659 if attr in cls._children:
660 return cls._children[attr]
661
662 raise AttributeError(
663 "object '%s' has no attribute '%s'" % (cls.__name__, attr))
664
665 def __str__(cls):
666 return cls.__name__
667
668 # See ParamValue.cxx_predecls for description.
669 def cxx_predecls(cls, code):
670 code('#include "params/$cls.hh"')
671
672 def pybind_predecls(cls, code):
673 code('#include "${{cls.cxx_header}}"')
674
675 def pybind_decl(cls, code):
676 class_path = cls.cxx_class.split('::')
677 namespaces, classname = class_path[:-1], class_path[-1]
678 py_class_name = '_COLONS_'.join(class_path) if namespaces else \
679 classname;
680
681 # The 'local' attribute restricts us to the params declared in
682 # the object itself, not including inherited params (which
683 # will also be inherited from the base class's param struct
684 # here). Sort the params based on their key
685 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
686 ports = cls._ports.local
687
688 code('''#include "pybind11/pybind11.h"
689#include "pybind11/stl.h"
690
691#include "params/$cls.hh"
692#include "python/pybind11/core.hh"
693#include "sim/init.hh"
694#include "sim/sim_object.hh"
695
696#include "${{cls.cxx_header}}"
697
698''')
699
700 for param in params:
701 param.pybind_predecls(code)
702
703 code('''namespace py = pybind11;
704
705static void
706module_init(py::module &m_internal)
707{
708 py::module m = m_internal.def_submodule("param_${cls}");
709''')
710 code.indent()
711 if cls._base:
712 code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \
713 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \
714 'm, "${cls}Params")')
715 else:
716 code('py::class_<${cls}Params, ' \
717 'std::unique_ptr<${cls}Params, py::nodelete>>(' \
718 'm, "${cls}Params")')
719
720 code.indent()
721 if not hasattr(cls, 'abstract') or not cls.abstract:
722 code('.def(py::init<>())')
723 code('.def("create", &${cls}Params::create)')
724
725 param_exports = cls.cxx_param_exports + [
726 PyBindProperty(k)
727 for k, v in sorted(cls._params.local.items())
728 ] + [
729 PyBindProperty("port_%s_connection_count" % port.name)
730 for port in ports.values()
731 ]
732 for exp in param_exports:
733 exp.export(code, "%sParams" % cls)
734
735 code(';')
736 code()
737 code.dedent()
738
739 bases = []
740 if 'cxx_base' in cls._value_dict:
741 # If the c++ base class implied by python inheritance was
742 # overridden, use that value.
743 if cls.cxx_base:
744 bases.append(cls.cxx_base)
745 elif cls._base:
746 # If not and if there was a SimObject base, use its c++ class
747 # as this class' base.
748 bases.append(cls._base.cxx_class)
749 # Add in any extra bases that were requested.
750 bases.extend(cls.cxx_extra_bases)
751
752 if bases:
753 base_str = ", ".join(bases)
754 code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \
755 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
756 'm, "${py_class_name}")')
757 else:
758 code('py::class_<${{cls.cxx_class}}, ' \
759 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
760 'm, "${py_class_name}")')
761 code.indent()
762 for exp in cls.cxx_exports:
763 exp.export(code, cls.cxx_class)
764 code(';')
765 code.dedent()
766 code()
767 code.dedent()
768 code('}')
769 code()
770 code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
771 cls, cls._base.type if cls._base else "")
772
773
774 # Generate the C++ declaration (.hh file) for this SimObject's
775 # param struct. Called from src/SConscript.
776 def cxx_param_decl(cls, code):
777 # The 'local' attribute restricts us to the params declared in
778 # the object itself, not including inherited params (which
779 # will also be inherited from the base class's param struct
780 # here). Sort the params based on their key
781 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
782 ports = cls._ports.local
783 try:
784 ptypes = [p.ptype for p in params]
785 except:
786 print(cls, p, p.ptype_str)
787 print(params)
788 raise
789
790 class_path = cls._value_dict['cxx_class'].split('::')
791
792 code('''\
793#ifndef __PARAMS__${cls}__
794#define __PARAMS__${cls}__
795
796''')
797
798
799 # The base SimObject has a couple of params that get
800 # automatically set from Python without being declared through
801 # the normal Param mechanism; we slip them in here (needed
802 # predecls now, actual declarations below)
803 if cls == SimObject:
804 code('''#include <string>''')
805
806 # A forward class declaration is sufficient since we are just
807 # declaring a pointer.
808 for ns in class_path[:-1]:
809 code('namespace $ns {')
810 code('class $0;', class_path[-1])
811 for ns in reversed(class_path[:-1]):
812 code('} // namespace $ns')
813 code()
814
815 for param in params:
816 param.cxx_predecls(code)
817 for port in ports.values():
818 port.cxx_predecls(code)
819 code()
820
821 if cls._base:
822 code('#include "params/${{cls._base.type}}.hh"')
823 code()
824
825 for ptype in ptypes:
826 if issubclass(ptype, Enum):
827 code('#include "enums/${{ptype.__name__}}.hh"')
828 code()
829
830 # now generate the actual param struct
831 code("struct ${cls}Params")
832 if cls._base:
833 code(" : public ${{cls._base.type}}Params")
834 code("{")
835 if not hasattr(cls, 'abstract') or not cls.abstract:
836 if 'type' in cls.__dict__:
837 code(" ${{cls.cxx_type}} create();")
838
839 code.indent()
840 if cls == SimObject:
841 code('''
842 SimObjectParams() {}
843 virtual ~SimObjectParams() {}
844
845 std::string name;
846 ''')
847
848 for param in params:
849 param.cxx_decl(code)
850 for port in ports.values():
851 port.cxx_decl(code)
852
853 code.dedent()
854 code('};')
855
856 code()
857 code('#endif // __PARAMS__${cls}__')
858 return code
859
860 # Generate the C++ declaration/definition files for this SimObject's
861 # param struct to allow C++ initialisation
862 def cxx_config_param_file(cls, code, is_header):
863 createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header)
864 return code
865
866# This *temporary* definition is required to support calls from the
867# SimObject class definition to the MetaSimObject methods (in
868# particular _set_param, which gets called for parameters with default
869# values defined on the SimObject class itself). It will get
870# overridden by the permanent definition (which requires that
871# SimObject be defined) lower in this file.
872def isSimObjectOrVector(value):
873 return False
874
875def cxxMethod(*args, **kwargs):
876 """Decorator to export C++ functions to Python"""
877
878 def decorate(func):
879 name = func.__name__
880 override = kwargs.get("override", False)
881 cxx_name = kwargs.get("cxx_name", name)
882
883 args, varargs, keywords, defaults = inspect.getargspec(func)
884 if varargs or keywords:
885 raise ValueError("Wrapped methods must not contain variable " \
886 "arguments")
887
888 # Create tuples of (argument, default)
889 if defaults:
890 args = args[:-len(defaults)] + \
891 list(zip(args[-len(defaults):], defaults))
892 # Don't include self in the argument list to PyBind
893 args = args[1:]
894
895
896 @wraps(func)
897 def cxx_call(self, *args, **kwargs):
898 ccobj = self.getCCObject()
899 return getattr(ccobj, name)(*args, **kwargs)
900
901 @wraps(func)
902 def py_call(self, *args, **kwargs):
903 return func(self, *args, **kwargs)
904
905 f = py_call if override else cxx_call
906 f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args)
907
908 return f
909
910 if len(args) == 0:
911 return decorate
912 elif len(args) == 1 and len(kwargs) == 0:
913 return decorate(*args)
914 else:
915 raise TypeError("One argument and no kwargs, or only kwargs expected")
916
917# This class holds information about each simobject parameter
918# that should be displayed on the command line for use in the
919# configuration system.
920class ParamInfo(object):
921 def __init__(self, type, desc, type_str, example, default_val, access_str):
922 self.type = type
923 self.desc = desc
924 self.type_str = type_str
925 self.example_str = example
926 self.default_val = default_val
927 # The string representation used to access this param through python.
928 # The method to access this parameter presented on the command line may
929 # be different, so this needs to be stored for later use.
930 self.access_str = access_str
931 self.created = True
932
933 # Make it so we can only set attributes at initialization time
934 # and effectively make this a const object.
935 def __setattr__(self, name, value):
936 if not "created" in self.__dict__:
937 self.__dict__[name] = value
938
939class SimObjectCliWrapperException(Exception):
940 def __init__(self, message):
941 super(Exception, self).__init__(message)
942
943class SimObjectCliWrapper(object):
944 """
945 Wrapper class to restrict operations that may be done
946 from the command line on SimObjects.
947
948 Only parameters may be set, and only children may be accessed.
949
950 Slicing allows for multiple simultaneous assignment of items in
951 one statement.
952 """
953
954 def __init__(self, sim_objects):
955 self.__dict__['_sim_objects'] = list(sim_objects)
956
957 def __getattr__(self, key):
958 return SimObjectCliWrapper(sim_object._children[key]
959 for sim_object in self._sim_objects)
960
961 def __setattr__(self, key, val):
962 for sim_object in self._sim_objects:
963 if key in sim_object._params:
964 if sim_object._params[key].isCmdLineSettable():
965 setattr(sim_object, key, val)
966 else:
967 raise SimObjectCliWrapperException(
968 'tried to set or unsettable' \
969 'object parameter: ' + key)
970 else:
971 raise SimObjectCliWrapperException(
972 'tried to set or access non-existent' \
973 'object parameter: ' + key)
974
975 def __getitem__(self, idx):
976 """
977 Extends the list() semantics to also allow tuples,
978 for example object[1, 3] selects items 1 and 3.
979 """
980 out = []
981 if isinstance(idx, tuple):
982 for t in idx:
983 out.extend(self[t]._sim_objects)
984 else:
985 if isinstance(idx, int):
986 _range = range(idx, idx + 1)
987 elif not isinstance(idx, slice):
988 raise SimObjectCliWrapperException( \
989 'invalid index type: ' + repr(idx))
990 for sim_object in self._sim_objects:
991 if isinstance(idx, slice):
992 _range = range(*idx.indices(len(sim_object)))
993 out.extend(sim_object[i] for i in _range)
994 return SimObjectCliWrapper(out)
995
996# The SimObject class is the root of the special hierarchy. Most of
997# the code in this class deals with the configuration hierarchy itself
998# (parent/child node relationships).
999class SimObject(object):
1000 # Specify metaclass. Any class inheriting from SimObject will
1001 # get this metaclass.
1002 __metaclass__ = MetaSimObject
1003 type = 'SimObject'
1004 abstract = True
1005
1006 cxx_header = "sim/sim_object.hh"
1007 cxx_extra_bases = [ "Drainable", "Serializable" ]
1008 eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
1009
1010 cxx_exports = [
1011 PyBindMethod("init"),
1012 PyBindMethod("initState"),
1013 PyBindMethod("memInvalidate"),
1014 PyBindMethod("memWriteback"),
1015 PyBindMethod("regStats"),
1016 PyBindMethod("resetStats"),
1017 PyBindMethod("regProbePoints"),
1018 PyBindMethod("regProbeListeners"),
1019 PyBindMethod("startup"),
1020 ]
1021
1022 cxx_param_exports = [
1023 PyBindProperty("name"),
1024 ]
1025
1026 @cxxMethod
1027 def loadState(self, cp):
1028 """Load SimObject state from a checkpoint"""
1029 pass
1030
1031 # Returns a dict of all the option strings that can be
1032 # generated as command line options for this simobject instance
1033 # by tracing all reachable params in the top level instance and
1034 # any children it contains.
1035 def enumerateParams(self, flags_dict = {},
1036 cmd_line_str = "", access_str = ""):
1037 if hasattr(self, "_paramEnumed"):
1038 print("Cycle detected enumerating params")
1039 else:
1040 self._paramEnumed = True
1041 # Scan the children first to pick up all the objects in this SimObj
1042 for keys in self._children:
1043 child = self._children[keys]
1044 next_cmdline_str = cmd_line_str + keys
1045 next_access_str = access_str + keys
1046 if not isSimObjectVector(child):
1047 next_cmdline_str = next_cmdline_str + "."
1048 next_access_str = next_access_str + "."
1049 flags_dict = child.enumerateParams(flags_dict,
1050 next_cmdline_str,
1051 next_access_str)
1052
1053 # Go through the simple params in the simobject in this level
1054 # of the simobject hierarchy and save information about the
1055 # parameter to be used for generating and processing command line
1056 # options to the simulator to set these parameters.
1057 for keys,values in self._params.items():
1058 if values.isCmdLineSettable():
1059 type_str = ''
1060 ex_str = values.example_str()
1061 ptype = None
1062 if isinstance(values, VectorParamDesc):
1063 type_str = 'Vector_%s' % values.ptype_str
1064 ptype = values
1065 else:
1066 type_str = '%s' % values.ptype_str
1067 ptype = values.ptype
1068
1069 if keys in self._hr_values\
1070 and keys in self._values\
1071 and not isinstance(self._values[keys],
1072 m5.proxy.BaseProxy):
1073 cmd_str = cmd_line_str + keys
1074 acc_str = access_str + keys
1075 flags_dict[cmd_str] = ParamInfo(ptype,
1076 self._params[keys].desc, type_str, ex_str,
1077 values.pretty_print(self._hr_values[keys]),
1078 acc_str)
1079 elif not keys in self._hr_values\
1080 and not keys in self._values:
1081 # Empty param
1082 cmd_str = cmd_line_str + keys
1083 acc_str = access_str + keys
1084 flags_dict[cmd_str] = ParamInfo(ptype,
1085 self._params[keys].desc,
1086 type_str, ex_str, '', acc_str)
1087
1088 return flags_dict
1089
1090 # Initialize new instance. For objects with SimObject-valued
1091 # children, we need to recursively clone the classes represented
1092 # by those param values as well in a consistent "deep copy"-style
1093 # fashion. That is, we want to make sure that each instance is
1094 # cloned only once, and that if there are multiple references to
1095 # the same original object, we end up with the corresponding
1096 # cloned references all pointing to the same cloned instance.
1097 def __init__(self, **kwargs):
1098 ancestor = kwargs.get('_ancestor')
1099 memo_dict = kwargs.get('_memo')
1100 if memo_dict is None:
1101 # prepare to memoize any recursively instantiated objects
1102 memo_dict = {}
1103 elif ancestor:
1104 # memoize me now to avoid problems with recursive calls
1105 memo_dict[ancestor] = self
1106
1107 if not ancestor:
1108 ancestor = self.__class__
1109 ancestor._instantiated = True
1110
1111 # initialize required attributes
1112 self._parent = None
1113 self._name = None
1114 self._ccObject = None # pointer to C++ object
1115 self._ccParams = None
1116 self._instantiated = False # really "cloned"
1117
1118 # Clone children specified at class level. No need for a
1119 # multidict here since we will be cloning everything.
1120 # Do children before parameter values so that children that
1121 # are also param values get cloned properly.
1122 self._children = {}
1123 for key,val in ancestor._children.items():
1124 self.add_child(key, val(_memo=memo_dict))
1125
1126 # Inherit parameter values from class using multidict so
1127 # individual value settings can be overridden but we still
1128 # inherit late changes to non-overridden class values.
1129 self._values = multidict(ancestor._values)
1130 self._hr_values = multidict(ancestor._hr_values)
1131 # clone SimObject-valued parameters
1132 for key,val in ancestor._values.items():
1133 val = tryAsSimObjectOrVector(val)
1134 if val is not None:
1135 self._values[key] = val(_memo=memo_dict)
1136
1137 # clone port references. no need to use a multidict here
1138 # since we will be creating new references for all ports.
1139 self._port_refs = {}
1140 for key,val in ancestor._port_refs.items():
1141 self._port_refs[key] = val.clone(self, memo_dict)
1142 # apply attribute assignments from keyword args, if any
1143 for key,val in kwargs.items():
1144 setattr(self, key, val)
1145
1146 # "Clone" the current instance by creating another instance of
1147 # this instance's class, but that inherits its parameter values
1148 # and port mappings from the current instance. If we're in a
1149 # "deep copy" recursive clone, check the _memo dict to see if
1150 # we've already cloned this instance.
1151 def __call__(self, **kwargs):
1152 memo_dict = kwargs.get('_memo')
1153 if memo_dict is None:
1154 # no memo_dict: must be top-level clone operation.
1155 # this is only allowed at the root of a hierarchy
1156 if self._parent:
1157 raise RuntimeError("attempt to clone object %s " \
1158 "not at the root of a tree (parent = %s)" \
1159 % (self, self._parent))
1160 # create a new dict and use that.
1161 memo_dict = {}
1162 kwargs['_memo'] = memo_dict
1163 elif self in memo_dict:
1164 # clone already done & memoized
1165 return memo_dict[self]
1166 return self.__class__(_ancestor = self, **kwargs)
1167
1168 def _get_port_ref(self, attr):
1169 # Return reference that can be assigned to another port
1170 # via __setattr__. There is only ever one reference
1171 # object per port, but we create them lazily here.
1172 ref = self._port_refs.get(attr)
1173 if ref == None:
1174 ref = self._ports[attr].makeRef(self)
1175 self._port_refs[attr] = ref
1176 return ref
1177
1178 def __getattr__(self, attr):
1179 if attr in self._ports:
1180 return self._get_port_ref(attr)
1181
1182 if attr in self._values:
1183 return self._values[attr]
1184
1185 if attr in self._children:
1186 return self._children[attr]
1187
1188 # If the attribute exists on the C++ object, transparently
1189 # forward the reference there. This is typically used for
1190 # methods exported to Python (e.g., init(), and startup())
1191 if self._ccObject and hasattr(self._ccObject, attr):
1192 return getattr(self._ccObject, attr)
1193
1194 err_string = "object '%s' has no attribute '%s'" \
1195 % (self.__class__.__name__, attr)
1196
1197 if not self._ccObject:
1198 err_string += "\n (C++ object is not yet constructed," \
1199 " so wrapped C++ methods are unavailable.)"
1200
1201 raise AttributeError(err_string)
1202
1203 # Set attribute (called on foo.attr = value when foo is an
1204 # instance of class cls).
1205 def __setattr__(self, attr, value):
1206 # normal processing for private attributes
1207 if attr.startswith('_'):
1208 object.__setattr__(self, attr, value)
1209 return
1210
1211 if attr in self._ports:
1212 # set up port connection
1213 self._get_port_ref(attr).connect(value)
1214 return
1215
1216 param = self._params.get(attr)
1217 if param:
1218 try:
1219 hr_value = value
1220 value = param.convert(value)
1221 except Exception as e:
1222 msg = "%s\nError setting param %s.%s to %s\n" % \
1223 (e, self.__class__.__name__, attr, value)
1224 e.args = (msg, )
1225 raise
1226 self._values[attr] = value
1227 # implicitly parent unparented objects assigned as params
1228 if isSimObjectOrVector(value) and not value.has_parent():
1229 self.add_child(attr, value)
1230 # set the human-readable value dict if this is a param
1231 # with a literal value and is not being set as an object
1232 # or proxy.
1233 if not (isSimObjectOrVector(value) or\
1234 isinstance(value, m5.proxy.BaseProxy)):
1235 self._hr_values[attr] = hr_value
1236
1237 return
1238
1239 # if RHS is a SimObject, it's an implicit child assignment
1240 if isSimObjectOrSequence(value):
1241 self.add_child(attr, value)
1242 return
1243
1244 # no valid assignment... raise exception
1245 raise AttributeError("Class %s has no parameter %s" \
1246 % (self.__class__.__name__, attr))
1247
1248
1249 # this hack allows tacking a '[0]' onto parameters that may or may
1250 # not be vectors, and always getting the first element (e.g. cpus)
1251 def __getitem__(self, key):
1252 if key == 0:
1253 return self
1254 raise IndexError("Non-zero index '%s' to SimObject" % key)
1255
1256 # this hack allows us to iterate over a SimObject that may
1257 # not be a vector, so we can call a loop over it and get just one
1258 # element.
1259 def __len__(self):
1260 return 1
1261
1262 # Also implemented by SimObjectVector
1263 def clear_parent(self, old_parent):
1264 assert self._parent is old_parent
1265 self._parent = None
1266
1267 # Also implemented by SimObjectVector
1268 def set_parent(self, parent, name):
1269 self._parent = parent
1270 self._name = name
1271
1272 # Return parent object of this SimObject, not implemented by
1273 # SimObjectVector because the elements in a SimObjectVector may not share
1274 # the same parent
1275 def get_parent(self):
1276 return self._parent
1277
1278 # Also implemented by SimObjectVector
1279 def get_name(self):
1280 return self._name
1281
1282 # Also implemented by SimObjectVector
1283 def has_parent(self):
1284 return self._parent is not None
1285
1286 # clear out child with given name. This code is not likely to be exercised.
1287 # See comment in add_child.
1288 def clear_child(self, name):
1289 child = self._children[name]
1290 child.clear_parent(self)
1291 del self._children[name]
1292
1293 # Add a new child to this object.
1294 def add_child(self, name, child):
1295 child = coerceSimObjectOrVector(child)
1296 if child.has_parent():
1297 warn("add_child('%s'): child '%s' already has parent", name,
1298 child.get_name())
1299 if name in self._children:
1300 # This code path had an undiscovered bug that would make it fail
1301 # at runtime. It had been here for a long time and was only
1302 # exposed by a buggy script. Changes here will probably not be
1303 # exercised without specialized testing.
1304 self.clear_child(name)
1305 child.set_parent(self, name)
1306 if not isNullPointer(child):
1307 self._children[name] = child
1308
1309 # Take SimObject-valued parameters that haven't been explicitly
1310 # assigned as children and make them children of the object that
1311 # they were assigned to as a parameter value. This guarantees
1312 # that when we instantiate all the parameter objects we're still
1313 # inside the configuration hierarchy.
1314 def adoptOrphanParams(self):
1315 for key,val in self._values.items():
1316 if not isSimObjectVector(val) and isSimObjectSequence(val):
1317 # need to convert raw SimObject sequences to
1318 # SimObjectVector class so we can call has_parent()
1319 val = SimObjectVector(val)
1320 self._values[key] = val
1321 if isSimObjectOrVector(val) and not val.has_parent():
1322 warn("%s adopting orphan SimObject param '%s'", self, key)
1323 self.add_child(key, val)
1324
1325 def path(self):
1326 if not self._parent:
1327 return '<orphan %s>' % self.__class__
1328 elif isinstance(self._parent, MetaSimObject):
1329 return str(self.__class__)
1330
1331 ppath = self._parent.path()
1332 if ppath == 'root':
1333 return self._name
1334 return ppath + "." + self._name
1335
1336 def __str__(self):
1337 return self.path()
1338
1339 def config_value(self):
1340 return self.path()
1341
1342 def ini_str(self):
1343 return self.path()
1344
1345 def find_any(self, ptype):
1346 if isinstance(self, ptype):
1347 return self, True
1348
1349 found_obj = None
1350 for child in self._children.values():
1351 visited = False
1352 if hasattr(child, '_visited'):
1353 visited = getattr(child, '_visited')
1354
1355 if isinstance(child, ptype) and not visited:
1356 if found_obj != None and child != found_obj:
1357 raise AttributeError(
1358 'parent.any matched more than one: %s %s' % \
1359 (found_obj.path, child.path))
1360 found_obj = child
1361 # search param space
1362 for pname,pdesc in self._params.items():
1363 if issubclass(pdesc.ptype, ptype):
1364 match_obj = self._values[pname]
1365 if found_obj != None and found_obj != match_obj:
1366 raise AttributeError(
1367 'parent.any matched more than one: %s and %s' % \
1368 (found_obj.path, match_obj.path))
1369 found_obj = match_obj
1370 return found_obj, found_obj != None
1371
1372 def find_all(self, ptype):
1373 all = {}
1374 # search children
1375 for child in self._children.values():
1376 # a child could be a list, so ensure we visit each item
1377 if isinstance(child, list):
1378 children = child
1379 else:
1380 children = [child]
1381
1382 for child in children:
1383 if isinstance(child, ptype) and not isproxy(child) and \
1384 not isNullPointer(child):
1385 all[child] = True
1386 if isSimObject(child):
1387 # also add results from the child itself
1388 child_all, done = child.find_all(ptype)
1389 all.update(dict(zip(child_all, [done] * len(child_all))))
1390 # search param space
1391 for pname,pdesc in self._params.items():
1392 if issubclass(pdesc.ptype, ptype):
1393 match_obj = self._values[pname]
1394 if not isproxy(match_obj) and not isNullPointer(match_obj):
1395 all[match_obj] = True
1396 # Also make sure to sort the keys based on the objects' path to
1397 # ensure that the order is the same on all hosts
1398 return sorted(all.keys(), key = lambda o: o.path()), True
1399
1400 def unproxy(self, base):
1401 return self
1402
1403 def unproxyParams(self):
1404 for param in self._params.keys():
1405 value = self._values.get(param)
1406 if value != None and isproxy(value):
1407 try:
1408 value = value.unproxy(self)
1409 except:
1410 print("Error in unproxying param '%s' of %s" %
1411 (param, self.path()))
1412 raise
1413 setattr(self, param, value)
1414
1415 # Unproxy ports in sorted order so that 'append' operations on
1416 # vector ports are done in a deterministic fashion.
1417 port_names = list(self._ports.keys())
1418 port_names.sort()
1419 for port_name in port_names:
1420 port = self._port_refs.get(port_name)
1421 if port != None:
1422 port.unproxy(self)
1423
1424 def print_ini(self, ini_file):
1425 print('[' + self.path() + ']', file=ini_file) # .ini section header
1426
1427 instanceDict[self.path()] = self
1428
1429 if hasattr(self, 'type'):
1430 print('type=%s' % self.type, file=ini_file)
1431
1432 if len(self._children.keys()):
1433 print('children=%s' %
1434 ' '.join(self._children[n].get_name()
1435 for n in sorted(self._children.keys())),
1436 file=ini_file)
1437
1438 for param in sorted(self._params.keys()):
1439 value = self._values.get(param)
1440 if value != None:
1441 print('%s=%s' % (param, self._values[param].ini_str()),
1442 file=ini_file)
1443
1444 for port_name in sorted(self._ports.keys()):
1445 port = self._port_refs.get(port_name, None)
1446 if port != None:
1447 print('%s=%s' % (port_name, port.ini_str()), file=ini_file)
1448
1449 print(file=ini_file) # blank line between objects
1450
1451 # generate a tree of dictionaries expressing all the parameters in the
1452 # instantiated system for use by scripts that want to do power, thermal
1453 # visualization, and other similar tasks
1454 def get_config_as_dict(self):
1455 d = attrdict()
1456 if hasattr(self, 'type'):
1457 d.type = self.type
1458 if hasattr(self, 'cxx_class'):
1459 d.cxx_class = self.cxx_class
1460 # Add the name and path of this object to be able to link to
1461 # the stats
1462 d.name = self.get_name()
1463 d.path = self.path()
1464
1465 for param in sorted(self._params.keys()):
1466 value = self._values.get(param)
1467 if value != None:
1468 d[param] = value.config_value()
1469
1470 for n in sorted(self._children.keys()):
1471 child = self._children[n]
1472 # Use the name of the attribute (and not get_name()) as
1473 # the key in the JSON dictionary to capture the hierarchy
1474 # in the Python code that assembled this system
1475 d[n] = child.get_config_as_dict()
1476
1477 for port_name in sorted(self._ports.keys()):
1478 port = self._port_refs.get(port_name, None)
1479 if port != None:
1480 # Represent each port with a dictionary containing the
1481 # prominent attributes
1482 d[port_name] = port.get_config_as_dict()
1483
1484 return d
1485
1486 def getCCParams(self):
1487 if self._ccParams:
1488 return self._ccParams
1489
1490 cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type)
1491 cc_params = cc_params_struct()
1492 cc_params.name = str(self)
1493
1494 param_names = list(self._params.keys())
1495 param_names.sort()
1496 for param in param_names:
1497 value = self._values.get(param)
1498 if value is None:
1499 fatal("%s.%s without default or user set value",
1500 self.path(), param)
1501
1502 value = value.getValue()
1503 if isinstance(self._params[param], VectorParamDesc):
1504 assert isinstance(value, list)
1505 vec = getattr(cc_params, param)
1506 assert not len(vec)
1507 # Some types are exposed as opaque types. They support
1508 # the append operation unlike the automatically
1509 # wrapped types.
1510 if isinstance(vec, list):
1511 setattr(cc_params, param, list(value))
1512 else:
1513 for v in value:
1514 getattr(cc_params, param).append(v)
1515 else:
1516 setattr(cc_params, param, value)
1517
1518 port_names = list(self._ports.keys())
1519 port_names.sort()
1520 for port_name in port_names:
1521 port = self._port_refs.get(port_name, None)
1522 if port != None:
1523 port_count = len(port)
1524 else:
1525 port_count = 0
1526 setattr(cc_params, 'port_' + port_name + '_connection_count',
1527 port_count)
1528 self._ccParams = cc_params
1529 return self._ccParams
1530
1531 # Get C++ object corresponding to this object, calling C++ if
1532 # necessary to construct it. Does *not* recursively create
1533 # children.
1534 def getCCObject(self):
1535 if not self._ccObject:
1536 # Make sure this object is in the configuration hierarchy
1537 if not self._parent and not isRoot(self):
1538 raise RuntimeError("Attempt to instantiate orphan node")
1539 # Cycles in the configuration hierarchy are not supported. This
1540 # will catch the resulting recursion and stop.
1541 self._ccObject = -1
1542 if not self.abstract:
1543 params = self.getCCParams()
1544 self._ccObject = params.create()
1545 elif self._ccObject == -1:
1546 raise RuntimeError("%s: Cycle found in configuration hierarchy." \
1547 % self.path())
1548 return self._ccObject
1549
1550 def descendants(self):
1551 yield self
1552 # The order of the dict is implementation dependent, so sort
1553 # it based on the key (name) to ensure the order is the same
1554 # on all hosts
1555 for (name, child) in sorted(self._children.items()):
1556 for obj in child.descendants():
1557 yield obj
1558
1559 # Call C++ to create C++ object corresponding to this object
1560 def createCCObject(self):
1561 self.getCCParams()
1562 self.getCCObject() # force creation
1563
1564 def getValue(self):
1565 return self.getCCObject()
1566
1567 # Create C++ port connections corresponding to the connections in
1568 # _port_refs
1569 def connectPorts(self):
1570 # Sort the ports based on their attribute name to ensure the
1571 # order is the same on all hosts
1572 for (attr, portRef) in sorted(self._port_refs.items()):
1573 portRef.ccConnect()
1574
1575 # Default function for generating the device structure.
1576 # Can be overloaded by the inheriting class
1577 def generateDeviceTree(self, state):
1578 return # return without yielding anything
1579 yield # make this function a (null) generator
1580
1581 def recurseDeviceTree(self, state):
1582 for child in self._children.values():
1583 for item in child: # For looping over SimObjectVectors
1584 for dt in item.generateDeviceTree(state):
1585 yield dt
1586
1587 # On a separate method otherwise certain buggy Python versions
1588 # would fail with: SyntaxError: unqualified exec is not allowed
1589 # in function 'apply_config'
1590 def _apply_config_get_dict(self):
1591 return {
1592 child_name: SimObjectCliWrapper(
1593 iter(self._children[child_name]))
1594 for child_name in self._children
1595 }
1596
1597 def apply_config(self, params):
1598 """
1599 exec a list of Python code strings contained in params.
1600
1601 The only exposed globals to those strings are the child
1602 SimObjects of this node.
1603
1604 This function is intended to allow users to modify SimObject
1605 parameters from the command line with Python statements.
1606 """
1607 d = self._apply_config_get_dict()
1608 for param in params:
1609 exec(param, d)
1610
1611# Function to provide to C++ so it can look up instances based on paths
1612def resolveSimObject(name):
1613 obj = instanceDict[name]
1614 return obj.getCCObject()
1615
1616def isSimObject(value):
1617 return isinstance(value, SimObject)
1618
1619def isSimObjectClass(value):
1620 return issubclass(value, SimObject)
1621
1622def isSimObjectVector(value):
1623 return isinstance(value, SimObjectVector)
1624
1625def isSimObjectSequence(value):
1626 if not isinstance(value, (list, tuple)) or len(value) == 0:
1627 return False
1628
1629 for val in value:
1630 if not isNullPointer(val) and not isSimObject(val):
1631 return False
1632
1633 return True
1634
1635def isSimObjectOrSequence(value):
1636 return isSimObject(value) or isSimObjectSequence(value)
1637
1638def isRoot(obj):
1639 from m5.objects import Root
1640 return obj and obj is Root.getInstance()
1641
1642def isSimObjectOrVector(value):
1643 return isSimObject(value) or isSimObjectVector(value)
1644
1645def tryAsSimObjectOrVector(value):
1646 if isSimObjectOrVector(value):
1647 return value
1648 if isSimObjectSequence(value):
1649 return SimObjectVector(value)
1650 return None
1651
1652def coerceSimObjectOrVector(value):
1653 value = tryAsSimObjectOrVector(value)
1654 if value is None:
1655 raise TypeError("SimObject or SimObjectVector expected")
1656 return value
1657
1658baseClasses = allClasses.copy()
1659baseInstances = instanceDict.copy()
1660
1661def clear():
1662 global allClasses, instanceDict, noCxxHeader
1663
1664 allClasses = baseClasses.copy()
1665 instanceDict = baseInstances.copy()
1666 noCxxHeader = False
1667
1668# __all__ defines the list of symbols that get exported when
1669# 'from config import *' is invoked. Try to keep this reasonably
1670# short to avoid polluting other namespaces.
1671__all__ = [
1672 'SimObject',
1673 'cxxMethod',
1674 'PyBindMethod',
1675 'PyBindProperty',
1676]