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