SimObject.py (13778:318f777400e9) SimObject.py (13781:280e5206fd97)
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 'cxx_template_params' : list,
419 }
420 # Attributes that can be set any time
421 keywords = { 'check' : FunctionType }
422
423 # __new__ is called before __init__, and is where the statements
424 # in the body of the class definition get loaded into the class's
425 # __dict__. We intercept this to filter out parameter & port assignments
426 # and only allow "private" attributes to be passed to the base
427 # __new__ (starting with underscore).
428 def __new__(mcls, name, bases, dict):
429 assert name not in allClasses, "SimObject %s already present" % name
430
431 # Copy "private" attributes, functions, and classes to the
432 # official dict. Everything else goes in _init_dict to be
433 # filtered in __init__.
434 cls_dict = {}
435 value_dict = {}
436 cxx_exports = []
437 for key,val in dict.items():
438 try:
439 cxx_exports.append(getattr(val, "__pybind"))
440 except AttributeError:
441 pass
442
443 if public_value(key, val):
444 cls_dict[key] = val
445 else:
446 # must be a param/port setting
447 value_dict[key] = val
448 if 'abstract' not in value_dict:
449 value_dict['abstract'] = False
450 if 'cxx_extra_bases' not in value_dict:
451 value_dict['cxx_extra_bases'] = []
452 if 'cxx_exports' not in value_dict:
453 value_dict['cxx_exports'] = cxx_exports
454 else:
455 value_dict['cxx_exports'] += cxx_exports
456 if 'cxx_param_exports' not in value_dict:
457 value_dict['cxx_param_exports'] = []
458 if 'cxx_template_params' not in value_dict:
459 value_dict['cxx_template_params'] = []
460 cls_dict['_value_dict'] = value_dict
461 cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
462 if 'type' in value_dict:
463 allClasses[name] = cls
464 return cls
465
466 # subclass initialization
467 def __init__(cls, name, bases, dict):
468 # calls type.__init__()... I think that's a no-op, but leave
469 # it here just in case it's not.
470 super(MetaSimObject, cls).__init__(name, bases, dict)
471
472 # initialize required attributes
473
474 # class-only attributes
475 cls._params = multidict() # param descriptions
476 cls._ports = multidict() # port descriptions
477
478 # class or instance attributes
479 cls._values = multidict() # param values
480 cls._hr_values = multidict() # human readable param values
481 cls._children = multidict() # SimObject children
482 cls._port_refs = multidict() # port ref objects
483 cls._instantiated = False # really instantiated, cloned, or subclassed
484
485 # We don't support multiple inheritance of sim objects. If you want
486 # to, you must fix multidict to deal with it properly. Non sim-objects
487 # are ok, though
488 bTotal = 0
489 for c in bases:
490 if isinstance(c, MetaSimObject):
491 bTotal += 1
492 if bTotal > 1:
493 raise TypeError(
494 "SimObjects do not support multiple inheritance")
495
496 base = bases[0]
497
498 # Set up general inheritance via multidicts. A subclass will
499 # inherit all its settings from the base class. The only time
500 # the following is not true is when we define the SimObject
501 # class itself (in which case the multidicts have no parent).
502 if isinstance(base, MetaSimObject):
503 cls._base = base
504 cls._params.parent = base._params
505 cls._ports.parent = base._ports
506 cls._values.parent = base._values
507 cls._hr_values.parent = base._hr_values
508 cls._children.parent = base._children
509 cls._port_refs.parent = base._port_refs
510 # mark base as having been subclassed
511 base._instantiated = True
512 else:
513 cls._base = None
514
515 # default keyword values
516 if 'type' in cls._value_dict:
517 if 'cxx_class' not in cls._value_dict:
518 cls._value_dict['cxx_class'] = cls._value_dict['type']
519
520 cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class']
521
522 if 'cxx_header' not in cls._value_dict:
523 global noCxxHeader
524 noCxxHeader = True
525 warn("No header file specified for SimObject: %s", name)
526
527 # Now process the _value_dict items. They could be defining
528 # new (or overriding existing) parameters or ports, setting
529 # class keywords (e.g., 'abstract'), or setting parameter
530 # values or port bindings. The first 3 can only be set when
531 # the class is defined, so we handle them here. The others
532 # can be set later too, so just emulate that by calling
533 # setattr().
534 for key,val in cls._value_dict.items():
535 # param descriptions
536 if isinstance(val, ParamDesc):
537 cls._new_param(key, val)
538
539 # port objects
540 elif isinstance(val, Port):
541 cls._new_port(key, val)
542
543 # init-time-only keywords
544 elif key in cls.init_keywords:
545 cls._set_keyword(key, val, cls.init_keywords[key])
546
547 # default: use normal path (ends up in __setattr__)
548 else:
549 setattr(cls, key, val)
550
551 def _set_keyword(cls, keyword, val, kwtype):
552 if not isinstance(val, kwtype):
553 raise TypeError('keyword %s has bad type %s (expecting %s)' % \
554 (keyword, type(val), kwtype))
555 if isinstance(val, FunctionType):
556 val = classmethod(val)
557 type.__setattr__(cls, keyword, val)
558
559 def _new_param(cls, name, pdesc):
560 # each param desc should be uniquely assigned to one variable
561 assert(not hasattr(pdesc, 'name'))
562 pdesc.name = name
563 cls._params[name] = pdesc
564 if hasattr(pdesc, 'default'):
565 cls._set_param(name, pdesc.default, pdesc)
566
567 def _set_param(cls, name, value, param):
568 assert(param.name == name)
569 try:
570 hr_value = value
571 value = param.convert(value)
572 except Exception as e:
573 msg = "%s\nError setting param %s.%s to %s\n" % \
574 (e, cls.__name__, name, value)
575 e.args = (msg, )
576 raise
577 cls._values[name] = value
578 # if param value is a SimObject, make it a child too, so that
579 # it gets cloned properly when the class is instantiated
580 if isSimObjectOrVector(value) and not value.has_parent():
581 cls._add_cls_child(name, value)
582 # update human-readable values of the param if it has a literal
583 # value and is not an object or proxy.
584 if not (isSimObjectOrVector(value) or\
585 isinstance(value, m5.proxy.BaseProxy)):
586 cls._hr_values[name] = hr_value
587
588 def _add_cls_child(cls, name, child):
589 # It's a little funky to have a class as a parent, but these
590 # objects should never be instantiated (only cloned, which
591 # clears the parent pointer), and this makes it clear that the
592 # object is not an orphan and can provide better error
593 # messages.
594 child.set_parent(cls, name)
595 if not isNullPointer(child):
596 cls._children[name] = child
597
598 def _new_port(cls, name, port):
599 # each port should be uniquely assigned to one variable
600 assert(not hasattr(port, 'name'))
601 port.name = name
602 cls._ports[name] = port
603
604 # same as _get_port_ref, effectively, but for classes
605 def _cls_get_port_ref(cls, attr):
606 # Return reference that can be assigned to another port
607 # via __setattr__. There is only ever one reference
608 # object per port, but we create them lazily here.
609 ref = cls._port_refs.get(attr)
610 if not ref:
611 ref = cls._ports[attr].makeRef(cls)
612 cls._port_refs[attr] = ref
613 return ref
614
615 # Set attribute (called on foo.attr = value when foo is an
616 # instance of class cls).
617 def __setattr__(cls, attr, value):
618 # normal processing for private attributes
619 if public_value(attr, value):
620 type.__setattr__(cls, attr, value)
621 return
622
623 if attr in cls.keywords:
624 cls._set_keyword(attr, value, cls.keywords[attr])
625 return
626
627 if attr in cls._ports:
628 cls._cls_get_port_ref(attr).connect(value)
629 return
630
631 if isSimObjectOrSequence(value) and cls._instantiated:
632 raise RuntimeError(
633 "cannot set SimObject parameter '%s' after\n" \
634 " class %s has been instantiated or subclassed" \
635 % (attr, cls.__name__))
636
637 # check for param
638 param = cls._params.get(attr)
639 if param:
640 cls._set_param(attr, value, param)
641 return
642
643 if isSimObjectOrSequence(value):
644 # If RHS is a SimObject, it's an implicit child assignment.
645 cls._add_cls_child(attr, coerceSimObjectOrVector(value))
646 return
647
648 # no valid assignment... raise exception
649 raise AttributeError(
650 "Class %s has no parameter \'%s\'" % (cls.__name__, attr))
651
652 def __getattr__(cls, attr):
653 if attr == 'cxx_class_path':
654 return cls.cxx_class.split('::')
655
656 if attr == 'cxx_class_name':
657 return cls.cxx_class_path[-1]
658
659 if attr == 'cxx_namespaces':
660 return cls.cxx_class_path[:-1]
661
662 if attr in cls._values:
663 return cls._values[attr]
664
665 if attr in cls._children:
666 return cls._children[attr]
667
668 raise AttributeError(
669 "object '%s' has no attribute '%s'" % (cls.__name__, attr))
670
671 def __str__(cls):
672 return cls.__name__
673
674 # See ParamValue.cxx_predecls for description.
675 def cxx_predecls(cls, code):
676 code('#include "params/$cls.hh"')
677
678 def pybind_predecls(cls, code):
679 code('#include "${{cls.cxx_header}}"')
680
681 def pybind_decl(cls, code):
682 class_path = cls.cxx_class.split('::')
683 namespaces, classname = class_path[:-1], class_path[-1]
684 py_class_name = '_COLONS_'.join(class_path) if namespaces else \
685 classname;
686
687 # The 'local' attribute restricts us to the params declared in
688 # the object itself, not including inherited params (which
689 # will also be inherited from the base class's param struct
690 # here). Sort the params based on their key
691 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
692 ports = cls._ports.local
693
694 code('''#include "pybind11/pybind11.h"
695#include "pybind11/stl.h"
696
697#include "params/$cls.hh"
698#include "python/pybind11/core.hh"
699#include "sim/init.hh"
700#include "sim/sim_object.hh"
701
702#include "${{cls.cxx_header}}"
703
704''')
705
706 for param in params:
707 param.pybind_predecls(code)
708
709 code('''namespace py = pybind11;
710
711static void
712module_init(py::module &m_internal)
713{
714 py::module m = m_internal.def_submodule("param_${cls}");
715''')
716 code.indent()
717 if cls._base:
718 code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \
719 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \
720 'm, "${cls}Params")')
721 else:
722 code('py::class_<${cls}Params, ' \
723 'std::unique_ptr<${cls}Params, py::nodelete>>(' \
724 'm, "${cls}Params")')
725
726 code.indent()
727 if not hasattr(cls, 'abstract') or not cls.abstract:
728 code('.def(py::init<>())')
729 code('.def("create", &${cls}Params::create)')
730
731 param_exports = cls.cxx_param_exports + [
732 PyBindProperty(k)
733 for k, v in sorted(cls._params.local.items())
734 ] + [
735 PyBindProperty("port_%s_connection_count" % port.name)
736 for port in ports.values()
737 ]
738 for exp in param_exports:
739 exp.export(code, "%sParams" % cls)
740
741 code(';')
742 code()
743 code.dedent()
744
745 bases = []
746 if 'cxx_base' in cls._value_dict:
747 # If the c++ base class implied by python inheritance was
748 # overridden, use that value.
749 if cls.cxx_base:
750 bases.append(cls.cxx_base)
751 elif cls._base:
752 # If not and if there was a SimObject base, use its c++ class
753 # as this class' base.
754 bases.append(cls._base.cxx_class)
755 # Add in any extra bases that were requested.
756 bases.extend(cls.cxx_extra_bases)
757
758 if bases:
759 base_str = ", ".join(bases)
760 code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \
761 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
762 'm, "${py_class_name}")')
763 else:
764 code('py::class_<${{cls.cxx_class}}, ' \
765 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
766 'm, "${py_class_name}")')
767 code.indent()
768 for exp in cls.cxx_exports:
769 exp.export(code, cls.cxx_class)
770 code(';')
771 code.dedent()
772 code()
773 code.dedent()
774 code('}')
775 code()
776 code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
777 cls, cls._base.type if cls._base else "")
778
779 _warned_about_nested_templates = False
780
781 # Generate the C++ declaration (.hh file) for this SimObject's
782 # param struct. Called from src/SConscript.
783 def cxx_param_decl(cls, code):
784 # The 'local' attribute restricts us to the params declared in
785 # the object itself, not including inherited params (which
786 # will also be inherited from the base class's param struct
787 # here). Sort the params based on their key
788 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
789 ports = cls._ports.local
790 try:
791 ptypes = [p.ptype for p in params]
792 except:
793 print(cls, p, p.ptype_str)
794 print(params)
795 raise
796
797 class CxxClass(object):
798 def __init__(self, sig, template_params=[]):
799 # Split the signature into its constituent parts. This could
800 # potentially be done with regular expressions, but
801 # it's simple enough to pick appart a class signature
802 # manually.
803 parts = sig.split('<', 1)
804 base = parts[0]
805 t_args = []
806 if len(parts) > 1:
807 # The signature had template arguments.
808 text = parts[1].rstrip(' \t\n>')
809 arg = ''
810 # Keep track of nesting to avoid splitting on ","s embedded
811 # in the arguments themselves.
812 depth = 0
813 for c in text:
814 if c == '<':
815 depth = depth + 1
816 if depth > 0 and not \
817 self._warned_about_nested_templates:
818 self._warned_about_nested_templates = True
819 print('Nested template argument in cxx_class.'
820 ' This feature is largely untested and '
821 ' may not work.')
822 elif c == '>':
823 depth = depth - 1
824 elif c == ',' and depth == 0:
825 t_args.append(arg.strip())
826 arg = ''
827 else:
828 arg = arg + c
829 if arg:
830 t_args.append(arg.strip())
831 # Split the non-template part on :: boundaries.
832 class_path = base.split('::')
833
834 # The namespaces are everything except the last part of the
835 # class path.
836 self.namespaces = class_path[:-1]
837 # And the class name is the last part.
838 self.name = class_path[-1]
839
840 self.template_params = template_params
841 self.template_arguments = []
842 # Iterate through the template arguments and their values. This
843 # will likely break if parameter packs are used.
844 for arg, param in zip(t_args, template_params):
845 type_keys = ('class', 'typename')
846 # If a parameter is a type, parse it recursively. Otherwise
847 # assume it's a constant, and store it verbatim.
848 if any(param.strip().startswith(kw) for kw in type_keys):
849 self.template_arguments.append(CxxClass(arg))
850 else:
851 self.template_arguments.append(arg)
852
853 def declare(self, code):
854 # First declare any template argument types.
855 for arg in self.template_arguments:
856 if isinstance(arg, CxxClass):
857 arg.declare(code)
858 # Re-open the target namespace.
859 for ns in self.namespaces:
860 code('namespace $ns {')
861 # If this is a class template...
862 if self.template_params:
863 code('template <${{", ".join(self.template_params)}}>')
864 # The actual class declaration.
865 code('class ${{self.name}};')
866 # Close the target namespaces.
867 for ns in reversed(self.namespaces):
868 code('} // namespace $ns')
869
870 code('''\
871#ifndef __PARAMS__${cls}__
872#define __PARAMS__${cls}__
873
874''')
875
876
877 # The base SimObject has a couple of params that get
878 # automatically set from Python without being declared through
879 # the normal Param mechanism; we slip them in here (needed
880 # predecls now, actual declarations below)
881 if cls == SimObject:
882 code('''#include <string>''')
883
884 cxx_class = CxxClass(cls._value_dict['cxx_class'],
885 cls._value_dict['cxx_template_params'])
886
887 # A forward class declaration is sufficient since we are just
888 # declaring a pointer.
889 cxx_class.declare(code)
890
891 for param in params:
892 param.cxx_predecls(code)
893 for port in ports.values():
894 port.cxx_predecls(code)
895 code()
896
897 if cls._base:
898 code('#include "params/${{cls._base.type}}.hh"')
899 code()
900
901 for ptype in ptypes:
902 if issubclass(ptype, Enum):
903 code('#include "enums/${{ptype.__name__}}.hh"')
904 code()
905
906 # now generate the actual param struct
907 code("struct ${cls}Params")
908 if cls._base:
909 code(" : public ${{cls._base.type}}Params")
910 code("{")
911 if not hasattr(cls, 'abstract') or not cls.abstract:
912 if 'type' in cls.__dict__:
913 code(" ${{cls.cxx_type}} create();")
914
915 code.indent()
916 if cls == SimObject:
917 code('''
918 SimObjectParams() {}
919 virtual ~SimObjectParams() {}
920
921 std::string name;
922 ''')
923
924 for param in params:
925 param.cxx_decl(code)
926 for port in ports.values():
927 port.cxx_decl(code)
928
929 code.dedent()
930 code('};')
931
932 code()
933 code('#endif // __PARAMS__${cls}__')
934 return code
935
936 # Generate the C++ declaration/definition files for this SimObject's
937 # param struct to allow C++ initialisation
938 def cxx_config_param_file(cls, code, is_header):
939 createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header)
940 return code
941
942# This *temporary* definition is required to support calls from the
943# SimObject class definition to the MetaSimObject methods (in
944# particular _set_param, which gets called for parameters with default
945# values defined on the SimObject class itself). It will get
946# overridden by the permanent definition (which requires that
947# SimObject be defined) lower in this file.
948def isSimObjectOrVector(value):
949 return False
950
951def cxxMethod(*args, **kwargs):
952 """Decorator to export C++ functions to Python"""
953
954 def decorate(func):
955 name = func.__name__
956 override = kwargs.get("override", False)
957 cxx_name = kwargs.get("cxx_name", name)
958 return_value_policy = kwargs.get("return_value_policy", None)
959
960 args, varargs, keywords, defaults = inspect.getargspec(func)
961 if varargs or keywords:
962 raise ValueError("Wrapped methods must not contain variable " \
963 "arguments")
964
965 # Create tuples of (argument, default)
966 if defaults:
967 args = args[:-len(defaults)] + \
968 list(zip(args[-len(defaults):], defaults))
969 # Don't include self in the argument list to PyBind
970 args = args[1:]
971
972
973 @wraps(func)
974 def cxx_call(self, *args, **kwargs):
975 ccobj = self.getCCObject()
976 return getattr(ccobj, name)(*args, **kwargs)
977
978 @wraps(func)
979 def py_call(self, *args, **kwargs):
980 return func(self, *args, **kwargs)
981
982 f = py_call if override else cxx_call
983 f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args,
984 return_value_policy=return_value_policy)
985
986 return f
987
988 if len(args) == 0:
989 return decorate
990 elif len(args) == 1 and len(kwargs) == 0:
991 return decorate(*args)
992 else:
993 raise TypeError("One argument and no kwargs, or only kwargs expected")
994
995# This class holds information about each simobject parameter
996# that should be displayed on the command line for use in the
997# configuration system.
998class ParamInfo(object):
999 def __init__(self, type, desc, type_str, example, default_val, access_str):
1000 self.type = type
1001 self.desc = desc
1002 self.type_str = type_str
1003 self.example_str = example
1004 self.default_val = default_val
1005 # The string representation used to access this param through python.
1006 # The method to access this parameter presented on the command line may
1007 # be different, so this needs to be stored for later use.
1008 self.access_str = access_str
1009 self.created = True
1010
1011 # Make it so we can only set attributes at initialization time
1012 # and effectively make this a const object.
1013 def __setattr__(self, name, value):
1014 if not "created" in self.__dict__:
1015 self.__dict__[name] = value
1016
1017class SimObjectCliWrapperException(Exception):
1018 def __init__(self, message):
1019 super(Exception, self).__init__(message)
1020
1021class SimObjectCliWrapper(object):
1022 """
1023 Wrapper class to restrict operations that may be done
1024 from the command line on SimObjects.
1025
1026 Only parameters may be set, and only children may be accessed.
1027
1028 Slicing allows for multiple simultaneous assignment of items in
1029 one statement.
1030 """
1031
1032 def __init__(self, sim_objects):
1033 self.__dict__['_sim_objects'] = list(sim_objects)
1034
1035 def __getattr__(self, key):
1036 return SimObjectCliWrapper(sim_object._children[key]
1037 for sim_object in self._sim_objects)
1038
1039 def __setattr__(self, key, val):
1040 for sim_object in self._sim_objects:
1041 if key in sim_object._params:
1042 if sim_object._params[key].isCmdLineSettable():
1043 setattr(sim_object, key, val)
1044 else:
1045 raise SimObjectCliWrapperException(
1046 'tried to set or unsettable' \
1047 'object parameter: ' + key)
1048 else:
1049 raise SimObjectCliWrapperException(
1050 'tried to set or access non-existent' \
1051 'object parameter: ' + key)
1052
1053 def __getitem__(self, idx):
1054 """
1055 Extends the list() semantics to also allow tuples,
1056 for example object[1, 3] selects items 1 and 3.
1057 """
1058 out = []
1059 if isinstance(idx, tuple):
1060 for t in idx:
1061 out.extend(self[t]._sim_objects)
1062 else:
1063 if isinstance(idx, int):
1064 _range = range(idx, idx + 1)
1065 elif not isinstance(idx, slice):
1066 raise SimObjectCliWrapperException( \
1067 'invalid index type: ' + repr(idx))
1068 for sim_object in self._sim_objects:
1069 if isinstance(idx, slice):
1070 _range = range(*idx.indices(len(sim_object)))
1071 out.extend(sim_object[i] for i in _range)
1072 return SimObjectCliWrapper(out)
1073
1074# The SimObject class is the root of the special hierarchy. Most of
1075# the code in this class deals with the configuration hierarchy itself
1076# (parent/child node relationships).
1077class SimObject(object):
1078 # Specify metaclass. Any class inheriting from SimObject will
1079 # get this metaclass.
1080 __metaclass__ = MetaSimObject
1081 type = 'SimObject'
1082 abstract = True
1083
1084 cxx_header = "sim/sim_object.hh"
1085 cxx_extra_bases = [ "Drainable", "Serializable" ]
1086 eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
1087
1088 cxx_exports = [
1089 PyBindMethod("init"),
1090 PyBindMethod("initState"),
1091 PyBindMethod("memInvalidate"),
1092 PyBindMethod("memWriteback"),
1093 PyBindMethod("regStats"),
1094 PyBindMethod("resetStats"),
1095 PyBindMethod("regProbePoints"),
1096 PyBindMethod("regProbeListeners"),
1097 PyBindMethod("startup"),
1098 ]
1099
1100 cxx_param_exports = [
1101 PyBindProperty("name"),
1102 ]
1103
1104 @cxxMethod
1105 def loadState(self, cp):
1106 """Load SimObject state from a checkpoint"""
1107 pass
1108
1109 # Returns a dict of all the option strings that can be
1110 # generated as command line options for this simobject instance
1111 # by tracing all reachable params in the top level instance and
1112 # any children it contains.
1113 def enumerateParams(self, flags_dict = {},
1114 cmd_line_str = "", access_str = ""):
1115 if hasattr(self, "_paramEnumed"):
1116 print("Cycle detected enumerating params")
1117 else:
1118 self._paramEnumed = True
1119 # Scan the children first to pick up all the objects in this SimObj
1120 for keys in self._children:
1121 child = self._children[keys]
1122 next_cmdline_str = cmd_line_str + keys
1123 next_access_str = access_str + keys
1124 if not isSimObjectVector(child):
1125 next_cmdline_str = next_cmdline_str + "."
1126 next_access_str = next_access_str + "."
1127 flags_dict = child.enumerateParams(flags_dict,
1128 next_cmdline_str,
1129 next_access_str)
1130
1131 # Go through the simple params in the simobject in this level
1132 # of the simobject hierarchy and save information about the
1133 # parameter to be used for generating and processing command line
1134 # options to the simulator to set these parameters.
1135 for keys,values in self._params.items():
1136 if values.isCmdLineSettable():
1137 type_str = ''
1138 ex_str = values.example_str()
1139 ptype = None
1140 if isinstance(values, VectorParamDesc):
1141 type_str = 'Vector_%s' % values.ptype_str
1142 ptype = values
1143 else:
1144 type_str = '%s' % values.ptype_str
1145 ptype = values.ptype
1146
1147 if keys in self._hr_values\
1148 and keys in self._values\
1149 and not isinstance(self._values[keys],
1150 m5.proxy.BaseProxy):
1151 cmd_str = cmd_line_str + keys
1152 acc_str = access_str + keys
1153 flags_dict[cmd_str] = ParamInfo(ptype,
1154 self._params[keys].desc, type_str, ex_str,
1155 values.pretty_print(self._hr_values[keys]),
1156 acc_str)
1157 elif not keys in self._hr_values\
1158 and not keys in self._values:
1159 # Empty param
1160 cmd_str = cmd_line_str + keys
1161 acc_str = access_str + keys
1162 flags_dict[cmd_str] = ParamInfo(ptype,
1163 self._params[keys].desc,
1164 type_str, ex_str, '', acc_str)
1165
1166 return flags_dict
1167
1168 # Initialize new instance. For objects with SimObject-valued
1169 # children, we need to recursively clone the classes represented
1170 # by those param values as well in a consistent "deep copy"-style
1171 # fashion. That is, we want to make sure that each instance is
1172 # cloned only once, and that if there are multiple references to
1173 # the same original object, we end up with the corresponding
1174 # cloned references all pointing to the same cloned instance.
1175 def __init__(self, **kwargs):
1176 ancestor = kwargs.get('_ancestor')
1177 memo_dict = kwargs.get('_memo')
1178 if memo_dict is None:
1179 # prepare to memoize any recursively instantiated objects
1180 memo_dict = {}
1181 elif ancestor:
1182 # memoize me now to avoid problems with recursive calls
1183 memo_dict[ancestor] = self
1184
1185 if not ancestor:
1186 ancestor = self.__class__
1187 ancestor._instantiated = True
1188
1189 # initialize required attributes
1190 self._parent = None
1191 self._name = None
1192 self._ccObject = None # pointer to C++ object
1193 self._ccParams = None
1194 self._instantiated = False # really "cloned"
1195
1196 # Clone children specified at class level. No need for a
1197 # multidict here since we will be cloning everything.
1198 # Do children before parameter values so that children that
1199 # are also param values get cloned properly.
1200 self._children = {}
1201 for key,val in ancestor._children.items():
1202 self.add_child(key, val(_memo=memo_dict))
1203
1204 # Inherit parameter values from class using multidict so
1205 # individual value settings can be overridden but we still
1206 # inherit late changes to non-overridden class values.
1207 self._values = multidict(ancestor._values)
1208 self._hr_values = multidict(ancestor._hr_values)
1209 # clone SimObject-valued parameters
1210 for key,val in ancestor._values.items():
1211 val = tryAsSimObjectOrVector(val)
1212 if val is not None:
1213 self._values[key] = val(_memo=memo_dict)
1214
1215 # clone port references. no need to use a multidict here
1216 # since we will be creating new references for all ports.
1217 self._port_refs = {}
1218 for key,val in ancestor._port_refs.items():
1219 self._port_refs[key] = val.clone(self, memo_dict)
1220 # apply attribute assignments from keyword args, if any
1221 for key,val in kwargs.items():
1222 setattr(self, key, val)
1223
1224 # "Clone" the current instance by creating another instance of
1225 # this instance's class, but that inherits its parameter values
1226 # and port mappings from the current instance. If we're in a
1227 # "deep copy" recursive clone, check the _memo dict to see if
1228 # we've already cloned this instance.
1229 def __call__(self, **kwargs):
1230 memo_dict = kwargs.get('_memo')
1231 if memo_dict is None:
1232 # no memo_dict: must be top-level clone operation.
1233 # this is only allowed at the root of a hierarchy
1234 if self._parent:
1235 raise RuntimeError("attempt to clone object %s " \
1236 "not at the root of a tree (parent = %s)" \
1237 % (self, self._parent))
1238 # create a new dict and use that.
1239 memo_dict = {}
1240 kwargs['_memo'] = memo_dict
1241 elif self in memo_dict:
1242 # clone already done & memoized
1243 return memo_dict[self]
1244 return self.__class__(_ancestor = self, **kwargs)
1245
1246 def _get_port_ref(self, attr):
1247 # Return reference that can be assigned to another port
1248 # via __setattr__. There is only ever one reference
1249 # object per port, but we create them lazily here.
1250 ref = self._port_refs.get(attr)
1251 if ref == None:
1252 ref = self._ports[attr].makeRef(self)
1253 self._port_refs[attr] = ref
1254 return ref
1255
1256 def __getattr__(self, attr):
1257 if attr in self._ports:
1258 return self._get_port_ref(attr)
1259
1260 if attr in self._values:
1261 return self._values[attr]
1262
1263 if attr in self._children:
1264 return self._children[attr]
1265
1266 # If the attribute exists on the C++ object, transparently
1267 # forward the reference there. This is typically used for
1268 # methods exported to Python (e.g., init(), and startup())
1269 if self._ccObject and hasattr(self._ccObject, attr):
1270 return getattr(self._ccObject, attr)
1271
1272 err_string = "object '%s' has no attribute '%s'" \
1273 % (self.__class__.__name__, attr)
1274
1275 if not self._ccObject:
1276 err_string += "\n (C++ object is not yet constructed," \
1277 " so wrapped C++ methods are unavailable.)"
1278
1279 raise AttributeError(err_string)
1280
1281 # Set attribute (called on foo.attr = value when foo is an
1282 # instance of class cls).
1283 def __setattr__(self, attr, value):
1284 # normal processing for private attributes
1285 if attr.startswith('_'):
1286 object.__setattr__(self, attr, value)
1287 return
1288
1289 if attr in self._ports:
1290 # set up port connection
1291 self._get_port_ref(attr).connect(value)
1292 return
1293
1294 param = self._params.get(attr)
1295 if param:
1296 try:
1297 hr_value = value
1298 value = param.convert(value)
1299 except Exception as e:
1300 msg = "%s\nError setting param %s.%s to %s\n" % \
1301 (e, self.__class__.__name__, attr, value)
1302 e.args = (msg, )
1303 raise
1304 self._values[attr] = value
1305 # implicitly parent unparented objects assigned as params
1306 if isSimObjectOrVector(value) and not value.has_parent():
1307 self.add_child(attr, value)
1308 # set the human-readable value dict if this is a param
1309 # with a literal value and is not being set as an object
1310 # or proxy.
1311 if not (isSimObjectOrVector(value) or\
1312 isinstance(value, m5.proxy.BaseProxy)):
1313 self._hr_values[attr] = hr_value
1314
1315 return
1316
1317 # if RHS is a SimObject, it's an implicit child assignment
1318 if isSimObjectOrSequence(value):
1319 self.add_child(attr, value)
1320 return
1321
1322 # no valid assignment... raise exception
1323 raise AttributeError("Class %s has no parameter %s" \
1324 % (self.__class__.__name__, attr))
1325
1326
1327 # this hack allows tacking a '[0]' onto parameters that may or may
1328 # not be vectors, and always getting the first element (e.g. cpus)
1329 def __getitem__(self, key):
1330 if key == 0:
1331 return self
1332 raise IndexError("Non-zero index '%s' to SimObject" % key)
1333
1334 # this hack allows us to iterate over a SimObject that may
1335 # not be a vector, so we can call a loop over it and get just one
1336 # element.
1337 def __len__(self):
1338 return 1
1339
1340 # Also implemented by SimObjectVector
1341 def clear_parent(self, old_parent):
1342 assert self._parent is old_parent
1343 self._parent = None
1344
1345 # Also implemented by SimObjectVector
1346 def set_parent(self, parent, name):
1347 self._parent = parent
1348 self._name = name
1349
1350 # Return parent object of this SimObject, not implemented by
1351 # SimObjectVector because the elements in a SimObjectVector may not share
1352 # the same parent
1353 def get_parent(self):
1354 return self._parent
1355
1356 # Also implemented by SimObjectVector
1357 def get_name(self):
1358 return self._name
1359
1360 # Also implemented by SimObjectVector
1361 def has_parent(self):
1362 return self._parent is not None
1363
1364 # clear out child with given name. This code is not likely to be exercised.
1365 # See comment in add_child.
1366 def clear_child(self, name):
1367 child = self._children[name]
1368 child.clear_parent(self)
1369 del self._children[name]
1370
1371 # Add a new child to this object.
1372 def add_child(self, name, child):
1373 child = coerceSimObjectOrVector(child)
1374 if child.has_parent():
1375 warn("add_child('%s'): child '%s' already has parent", name,
1376 child.get_name())
1377 if name in self._children:
1378 # This code path had an undiscovered bug that would make it fail
1379 # at runtime. It had been here for a long time and was only
1380 # exposed by a buggy script. Changes here will probably not be
1381 # exercised without specialized testing.
1382 self.clear_child(name)
1383 child.set_parent(self, name)
1384 if not isNullPointer(child):
1385 self._children[name] = child
1386
1387 # Take SimObject-valued parameters that haven't been explicitly
1388 # assigned as children and make them children of the object that
1389 # they were assigned to as a parameter value. This guarantees
1390 # that when we instantiate all the parameter objects we're still
1391 # inside the configuration hierarchy.
1392 def adoptOrphanParams(self):
1393 for key,val in self._values.items():
1394 if not isSimObjectVector(val) and isSimObjectSequence(val):
1395 # need to convert raw SimObject sequences to
1396 # SimObjectVector class so we can call has_parent()
1397 val = SimObjectVector(val)
1398 self._values[key] = val
1399 if isSimObjectOrVector(val) and not val.has_parent():
1400 warn("%s adopting orphan SimObject param '%s'", self, key)
1401 self.add_child(key, val)
1402
1403 def path(self):
1404 if not self._parent:
1405 return '<orphan %s>' % self.__class__
1406 elif isinstance(self._parent, MetaSimObject):
1407 return str(self.__class__)
1408
1409 ppath = self._parent.path()
1410 if ppath == 'root':
1411 return self._name
1412 return ppath + "." + self._name
1413
1414 def __str__(self):
1415 return self.path()
1416
1417 def config_value(self):
1418 return self.path()
1419
1420 def ini_str(self):
1421 return self.path()
1422
1423 def find_any(self, ptype):
1424 if isinstance(self, ptype):
1425 return self, True
1426
1427 found_obj = None
1428 for child in self._children.values():
1429 visited = False
1430 if hasattr(child, '_visited'):
1431 visited = getattr(child, '_visited')
1432
1433 if isinstance(child, ptype) and not visited:
1434 if found_obj != None and child != found_obj:
1435 raise AttributeError(
1436 'parent.any matched more than one: %s %s' % \
1437 (found_obj.path, child.path))
1438 found_obj = child
1439 # search param space
1440 for pname,pdesc in self._params.items():
1441 if issubclass(pdesc.ptype, ptype):
1442 match_obj = self._values[pname]
1443 if found_obj != None and found_obj != match_obj:
1444 raise AttributeError(
1445 'parent.any matched more than one: %s and %s' % \
1446 (found_obj.path, match_obj.path))
1447 found_obj = match_obj
1448 return found_obj, found_obj != None
1449
1450 def find_all(self, ptype):
1451 all = {}
1452 # search children
1453 for child in self._children.values():
1454 # a child could be a list, so ensure we visit each item
1455 if isinstance(child, list):
1456 children = child
1457 else:
1458 children = [child]
1459
1460 for child in children:
1461 if isinstance(child, ptype) and not isproxy(child) and \
1462 not isNullPointer(child):
1463 all[child] = True
1464 if isSimObject(child):
1465 # also add results from the child itself
1466 child_all, done = child.find_all(ptype)
1467 all.update(dict(zip(child_all, [done] * len(child_all))))
1468 # search param space
1469 for pname,pdesc in self._params.items():
1470 if issubclass(pdesc.ptype, ptype):
1471 match_obj = self._values[pname]
1472 if not isproxy(match_obj) and not isNullPointer(match_obj):
1473 all[match_obj] = True
1474 # Also make sure to sort the keys based on the objects' path to
1475 # ensure that the order is the same on all hosts
1476 return sorted(all.keys(), key = lambda o: o.path()), True
1477
1478 def unproxy(self, base):
1479 return self
1480
1481 def unproxyParams(self):
1482 for param in self._params.keys():
1483 value = self._values.get(param)
1484 if value != None and isproxy(value):
1485 try:
1486 value = value.unproxy(self)
1487 except:
1488 print("Error in unproxying param '%s' of %s" %
1489 (param, self.path()))
1490 raise
1491 setattr(self, param, value)
1492
1493 # Unproxy ports in sorted order so that 'append' operations on
1494 # vector ports are done in a deterministic fashion.
1495 port_names = list(self._ports.keys())
1496 port_names.sort()
1497 for port_name in port_names:
1498 port = self._port_refs.get(port_name)
1499 if port != None:
1500 port.unproxy(self)
1501
1502 def print_ini(self, ini_file):
1503 print('[' + self.path() + ']', file=ini_file) # .ini section header
1504
1505 instanceDict[self.path()] = self
1506
1507 if hasattr(self, 'type'):
1508 print('type=%s' % self.type, file=ini_file)
1509
1510 if len(self._children.keys()):
1511 print('children=%s' %
1512 ' '.join(self._children[n].get_name()
1513 for n in sorted(self._children.keys())),
1514 file=ini_file)
1515
1516 for param in sorted(self._params.keys()):
1517 value = self._values.get(param)
1518 if value != None:
1519 print('%s=%s' % (param, self._values[param].ini_str()),
1520 file=ini_file)
1521
1522 for port_name in sorted(self._ports.keys()):
1523 port = self._port_refs.get(port_name, None)
1524 if port != None:
1525 print('%s=%s' % (port_name, port.ini_str()), file=ini_file)
1526
1527 print(file=ini_file) # blank line between objects
1528
1529 # generate a tree of dictionaries expressing all the parameters in the
1530 # instantiated system for use by scripts that want to do power, thermal
1531 # visualization, and other similar tasks
1532 def get_config_as_dict(self):
1533 d = attrdict()
1534 if hasattr(self, 'type'):
1535 d.type = self.type
1536 if hasattr(self, 'cxx_class'):
1537 d.cxx_class = self.cxx_class
1538 # Add the name and path of this object to be able to link to
1539 # the stats
1540 d.name = self.get_name()
1541 d.path = self.path()
1542
1543 for param in sorted(self._params.keys()):
1544 value = self._values.get(param)
1545 if value != None:
1546 d[param] = value.config_value()
1547
1548 for n in sorted(self._children.keys()):
1549 child = self._children[n]
1550 # Use the name of the attribute (and not get_name()) as
1551 # the key in the JSON dictionary to capture the hierarchy
1552 # in the Python code that assembled this system
1553 d[n] = child.get_config_as_dict()
1554
1555 for port_name in sorted(self._ports.keys()):
1556 port = self._port_refs.get(port_name, None)
1557 if port != None:
1558 # Represent each port with a dictionary containing the
1559 # prominent attributes
1560 d[port_name] = port.get_config_as_dict()
1561
1562 return d
1563
1564 def getCCParams(self):
1565 if self._ccParams:
1566 return self._ccParams
1567
1568 cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type)
1569 cc_params = cc_params_struct()
1570 cc_params.name = str(self)
1571
1572 param_names = list(self._params.keys())
1573 param_names.sort()
1574 for param in param_names:
1575 value = self._values.get(param)
1576 if value is None:
1577 fatal("%s.%s without default or user set value",
1578 self.path(), param)
1579
1580 value = value.getValue()
1581 if isinstance(self._params[param], VectorParamDesc):
1582 assert isinstance(value, list)
1583 vec = getattr(cc_params, param)
1584 assert not len(vec)
1585 # Some types are exposed as opaque types. They support
1586 # the append operation unlike the automatically
1587 # wrapped types.
1588 if isinstance(vec, list):
1589 setattr(cc_params, param, list(value))
1590 else:
1591 for v in value:
1592 getattr(cc_params, param).append(v)
1593 else:
1594 setattr(cc_params, param, value)
1595
1596 port_names = list(self._ports.keys())
1597 port_names.sort()
1598 for port_name in port_names:
1599 port = self._port_refs.get(port_name, None)
1600 if port != None:
1601 port_count = len(port)
1602 else:
1603 port_count = 0
1604 setattr(cc_params, 'port_' + port_name + '_connection_count',
1605 port_count)
1606 self._ccParams = cc_params
1607 return self._ccParams
1608
1609 # Get C++ object corresponding to this object, calling C++ if
1610 # necessary to construct it. Does *not* recursively create
1611 # children.
1612 def getCCObject(self):
1613 if not self._ccObject:
1614 # Make sure this object is in the configuration hierarchy
1615 if not self._parent and not isRoot(self):
1616 raise RuntimeError("Attempt to instantiate orphan node")
1617 # Cycles in the configuration hierarchy are not supported. This
1618 # will catch the resulting recursion and stop.
1619 self._ccObject = -1
1620 if not self.abstract:
1621 params = self.getCCParams()
1622 self._ccObject = params.create()
1623 elif self._ccObject == -1:
1624 raise RuntimeError("%s: Cycle found in configuration hierarchy." \
1625 % self.path())
1626 return self._ccObject
1627
1628 def descendants(self):
1629 yield self
1630 # The order of the dict is implementation dependent, so sort
1631 # it based on the key (name) to ensure the order is the same
1632 # on all hosts
1633 for (name, child) in sorted(self._children.items()):
1634 for obj in child.descendants():
1635 yield obj
1636
1637 # Call C++ to create C++ object corresponding to this object
1638 def createCCObject(self):
1639 self.getCCParams()
1640 self.getCCObject() # force creation
1641
1642 def getValue(self):
1643 return self.getCCObject()
1644
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 'cxx_template_params' : list,
419 }
420 # Attributes that can be set any time
421 keywords = { 'check' : FunctionType }
422
423 # __new__ is called before __init__, and is where the statements
424 # in the body of the class definition get loaded into the class's
425 # __dict__. We intercept this to filter out parameter & port assignments
426 # and only allow "private" attributes to be passed to the base
427 # __new__ (starting with underscore).
428 def __new__(mcls, name, bases, dict):
429 assert name not in allClasses, "SimObject %s already present" % name
430
431 # Copy "private" attributes, functions, and classes to the
432 # official dict. Everything else goes in _init_dict to be
433 # filtered in __init__.
434 cls_dict = {}
435 value_dict = {}
436 cxx_exports = []
437 for key,val in dict.items():
438 try:
439 cxx_exports.append(getattr(val, "__pybind"))
440 except AttributeError:
441 pass
442
443 if public_value(key, val):
444 cls_dict[key] = val
445 else:
446 # must be a param/port setting
447 value_dict[key] = val
448 if 'abstract' not in value_dict:
449 value_dict['abstract'] = False
450 if 'cxx_extra_bases' not in value_dict:
451 value_dict['cxx_extra_bases'] = []
452 if 'cxx_exports' not in value_dict:
453 value_dict['cxx_exports'] = cxx_exports
454 else:
455 value_dict['cxx_exports'] += cxx_exports
456 if 'cxx_param_exports' not in value_dict:
457 value_dict['cxx_param_exports'] = []
458 if 'cxx_template_params' not in value_dict:
459 value_dict['cxx_template_params'] = []
460 cls_dict['_value_dict'] = value_dict
461 cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
462 if 'type' in value_dict:
463 allClasses[name] = cls
464 return cls
465
466 # subclass initialization
467 def __init__(cls, name, bases, dict):
468 # calls type.__init__()... I think that's a no-op, but leave
469 # it here just in case it's not.
470 super(MetaSimObject, cls).__init__(name, bases, dict)
471
472 # initialize required attributes
473
474 # class-only attributes
475 cls._params = multidict() # param descriptions
476 cls._ports = multidict() # port descriptions
477
478 # class or instance attributes
479 cls._values = multidict() # param values
480 cls._hr_values = multidict() # human readable param values
481 cls._children = multidict() # SimObject children
482 cls._port_refs = multidict() # port ref objects
483 cls._instantiated = False # really instantiated, cloned, or subclassed
484
485 # We don't support multiple inheritance of sim objects. If you want
486 # to, you must fix multidict to deal with it properly. Non sim-objects
487 # are ok, though
488 bTotal = 0
489 for c in bases:
490 if isinstance(c, MetaSimObject):
491 bTotal += 1
492 if bTotal > 1:
493 raise TypeError(
494 "SimObjects do not support multiple inheritance")
495
496 base = bases[0]
497
498 # Set up general inheritance via multidicts. A subclass will
499 # inherit all its settings from the base class. The only time
500 # the following is not true is when we define the SimObject
501 # class itself (in which case the multidicts have no parent).
502 if isinstance(base, MetaSimObject):
503 cls._base = base
504 cls._params.parent = base._params
505 cls._ports.parent = base._ports
506 cls._values.parent = base._values
507 cls._hr_values.parent = base._hr_values
508 cls._children.parent = base._children
509 cls._port_refs.parent = base._port_refs
510 # mark base as having been subclassed
511 base._instantiated = True
512 else:
513 cls._base = None
514
515 # default keyword values
516 if 'type' in cls._value_dict:
517 if 'cxx_class' not in cls._value_dict:
518 cls._value_dict['cxx_class'] = cls._value_dict['type']
519
520 cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class']
521
522 if 'cxx_header' not in cls._value_dict:
523 global noCxxHeader
524 noCxxHeader = True
525 warn("No header file specified for SimObject: %s", name)
526
527 # Now process the _value_dict items. They could be defining
528 # new (or overriding existing) parameters or ports, setting
529 # class keywords (e.g., 'abstract'), or setting parameter
530 # values or port bindings. The first 3 can only be set when
531 # the class is defined, so we handle them here. The others
532 # can be set later too, so just emulate that by calling
533 # setattr().
534 for key,val in cls._value_dict.items():
535 # param descriptions
536 if isinstance(val, ParamDesc):
537 cls._new_param(key, val)
538
539 # port objects
540 elif isinstance(val, Port):
541 cls._new_port(key, val)
542
543 # init-time-only keywords
544 elif key in cls.init_keywords:
545 cls._set_keyword(key, val, cls.init_keywords[key])
546
547 # default: use normal path (ends up in __setattr__)
548 else:
549 setattr(cls, key, val)
550
551 def _set_keyword(cls, keyword, val, kwtype):
552 if not isinstance(val, kwtype):
553 raise TypeError('keyword %s has bad type %s (expecting %s)' % \
554 (keyword, type(val), kwtype))
555 if isinstance(val, FunctionType):
556 val = classmethod(val)
557 type.__setattr__(cls, keyword, val)
558
559 def _new_param(cls, name, pdesc):
560 # each param desc should be uniquely assigned to one variable
561 assert(not hasattr(pdesc, 'name'))
562 pdesc.name = name
563 cls._params[name] = pdesc
564 if hasattr(pdesc, 'default'):
565 cls._set_param(name, pdesc.default, pdesc)
566
567 def _set_param(cls, name, value, param):
568 assert(param.name == name)
569 try:
570 hr_value = value
571 value = param.convert(value)
572 except Exception as e:
573 msg = "%s\nError setting param %s.%s to %s\n" % \
574 (e, cls.__name__, name, value)
575 e.args = (msg, )
576 raise
577 cls._values[name] = value
578 # if param value is a SimObject, make it a child too, so that
579 # it gets cloned properly when the class is instantiated
580 if isSimObjectOrVector(value) and not value.has_parent():
581 cls._add_cls_child(name, value)
582 # update human-readable values of the param if it has a literal
583 # value and is not an object or proxy.
584 if not (isSimObjectOrVector(value) or\
585 isinstance(value, m5.proxy.BaseProxy)):
586 cls._hr_values[name] = hr_value
587
588 def _add_cls_child(cls, name, child):
589 # It's a little funky to have a class as a parent, but these
590 # objects should never be instantiated (only cloned, which
591 # clears the parent pointer), and this makes it clear that the
592 # object is not an orphan and can provide better error
593 # messages.
594 child.set_parent(cls, name)
595 if not isNullPointer(child):
596 cls._children[name] = child
597
598 def _new_port(cls, name, port):
599 # each port should be uniquely assigned to one variable
600 assert(not hasattr(port, 'name'))
601 port.name = name
602 cls._ports[name] = port
603
604 # same as _get_port_ref, effectively, but for classes
605 def _cls_get_port_ref(cls, attr):
606 # Return reference that can be assigned to another port
607 # via __setattr__. There is only ever one reference
608 # object per port, but we create them lazily here.
609 ref = cls._port_refs.get(attr)
610 if not ref:
611 ref = cls._ports[attr].makeRef(cls)
612 cls._port_refs[attr] = ref
613 return ref
614
615 # Set attribute (called on foo.attr = value when foo is an
616 # instance of class cls).
617 def __setattr__(cls, attr, value):
618 # normal processing for private attributes
619 if public_value(attr, value):
620 type.__setattr__(cls, attr, value)
621 return
622
623 if attr in cls.keywords:
624 cls._set_keyword(attr, value, cls.keywords[attr])
625 return
626
627 if attr in cls._ports:
628 cls._cls_get_port_ref(attr).connect(value)
629 return
630
631 if isSimObjectOrSequence(value) and cls._instantiated:
632 raise RuntimeError(
633 "cannot set SimObject parameter '%s' after\n" \
634 " class %s has been instantiated or subclassed" \
635 % (attr, cls.__name__))
636
637 # check for param
638 param = cls._params.get(attr)
639 if param:
640 cls._set_param(attr, value, param)
641 return
642
643 if isSimObjectOrSequence(value):
644 # If RHS is a SimObject, it's an implicit child assignment.
645 cls._add_cls_child(attr, coerceSimObjectOrVector(value))
646 return
647
648 # no valid assignment... raise exception
649 raise AttributeError(
650 "Class %s has no parameter \'%s\'" % (cls.__name__, attr))
651
652 def __getattr__(cls, attr):
653 if attr == 'cxx_class_path':
654 return cls.cxx_class.split('::')
655
656 if attr == 'cxx_class_name':
657 return cls.cxx_class_path[-1]
658
659 if attr == 'cxx_namespaces':
660 return cls.cxx_class_path[:-1]
661
662 if attr in cls._values:
663 return cls._values[attr]
664
665 if attr in cls._children:
666 return cls._children[attr]
667
668 raise AttributeError(
669 "object '%s' has no attribute '%s'" % (cls.__name__, attr))
670
671 def __str__(cls):
672 return cls.__name__
673
674 # See ParamValue.cxx_predecls for description.
675 def cxx_predecls(cls, code):
676 code('#include "params/$cls.hh"')
677
678 def pybind_predecls(cls, code):
679 code('#include "${{cls.cxx_header}}"')
680
681 def pybind_decl(cls, code):
682 class_path = cls.cxx_class.split('::')
683 namespaces, classname = class_path[:-1], class_path[-1]
684 py_class_name = '_COLONS_'.join(class_path) if namespaces else \
685 classname;
686
687 # The 'local' attribute restricts us to the params declared in
688 # the object itself, not including inherited params (which
689 # will also be inherited from the base class's param struct
690 # here). Sort the params based on their key
691 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
692 ports = cls._ports.local
693
694 code('''#include "pybind11/pybind11.h"
695#include "pybind11/stl.h"
696
697#include "params/$cls.hh"
698#include "python/pybind11/core.hh"
699#include "sim/init.hh"
700#include "sim/sim_object.hh"
701
702#include "${{cls.cxx_header}}"
703
704''')
705
706 for param in params:
707 param.pybind_predecls(code)
708
709 code('''namespace py = pybind11;
710
711static void
712module_init(py::module &m_internal)
713{
714 py::module m = m_internal.def_submodule("param_${cls}");
715''')
716 code.indent()
717 if cls._base:
718 code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \
719 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \
720 'm, "${cls}Params")')
721 else:
722 code('py::class_<${cls}Params, ' \
723 'std::unique_ptr<${cls}Params, py::nodelete>>(' \
724 'm, "${cls}Params")')
725
726 code.indent()
727 if not hasattr(cls, 'abstract') or not cls.abstract:
728 code('.def(py::init<>())')
729 code('.def("create", &${cls}Params::create)')
730
731 param_exports = cls.cxx_param_exports + [
732 PyBindProperty(k)
733 for k, v in sorted(cls._params.local.items())
734 ] + [
735 PyBindProperty("port_%s_connection_count" % port.name)
736 for port in ports.values()
737 ]
738 for exp in param_exports:
739 exp.export(code, "%sParams" % cls)
740
741 code(';')
742 code()
743 code.dedent()
744
745 bases = []
746 if 'cxx_base' in cls._value_dict:
747 # If the c++ base class implied by python inheritance was
748 # overridden, use that value.
749 if cls.cxx_base:
750 bases.append(cls.cxx_base)
751 elif cls._base:
752 # If not and if there was a SimObject base, use its c++ class
753 # as this class' base.
754 bases.append(cls._base.cxx_class)
755 # Add in any extra bases that were requested.
756 bases.extend(cls.cxx_extra_bases)
757
758 if bases:
759 base_str = ", ".join(bases)
760 code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \
761 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
762 'm, "${py_class_name}")')
763 else:
764 code('py::class_<${{cls.cxx_class}}, ' \
765 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
766 'm, "${py_class_name}")')
767 code.indent()
768 for exp in cls.cxx_exports:
769 exp.export(code, cls.cxx_class)
770 code(';')
771 code.dedent()
772 code()
773 code.dedent()
774 code('}')
775 code()
776 code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
777 cls, cls._base.type if cls._base else "")
778
779 _warned_about_nested_templates = False
780
781 # Generate the C++ declaration (.hh file) for this SimObject's
782 # param struct. Called from src/SConscript.
783 def cxx_param_decl(cls, code):
784 # The 'local' attribute restricts us to the params declared in
785 # the object itself, not including inherited params (which
786 # will also be inherited from the base class's param struct
787 # here). Sort the params based on their key
788 params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
789 ports = cls._ports.local
790 try:
791 ptypes = [p.ptype for p in params]
792 except:
793 print(cls, p, p.ptype_str)
794 print(params)
795 raise
796
797 class CxxClass(object):
798 def __init__(self, sig, template_params=[]):
799 # Split the signature into its constituent parts. This could
800 # potentially be done with regular expressions, but
801 # it's simple enough to pick appart a class signature
802 # manually.
803 parts = sig.split('<', 1)
804 base = parts[0]
805 t_args = []
806 if len(parts) > 1:
807 # The signature had template arguments.
808 text = parts[1].rstrip(' \t\n>')
809 arg = ''
810 # Keep track of nesting to avoid splitting on ","s embedded
811 # in the arguments themselves.
812 depth = 0
813 for c in text:
814 if c == '<':
815 depth = depth + 1
816 if depth > 0 and not \
817 self._warned_about_nested_templates:
818 self._warned_about_nested_templates = True
819 print('Nested template argument in cxx_class.'
820 ' This feature is largely untested and '
821 ' may not work.')
822 elif c == '>':
823 depth = depth - 1
824 elif c == ',' and depth == 0:
825 t_args.append(arg.strip())
826 arg = ''
827 else:
828 arg = arg + c
829 if arg:
830 t_args.append(arg.strip())
831 # Split the non-template part on :: boundaries.
832 class_path = base.split('::')
833
834 # The namespaces are everything except the last part of the
835 # class path.
836 self.namespaces = class_path[:-1]
837 # And the class name is the last part.
838 self.name = class_path[-1]
839
840 self.template_params = template_params
841 self.template_arguments = []
842 # Iterate through the template arguments and their values. This
843 # will likely break if parameter packs are used.
844 for arg, param in zip(t_args, template_params):
845 type_keys = ('class', 'typename')
846 # If a parameter is a type, parse it recursively. Otherwise
847 # assume it's a constant, and store it verbatim.
848 if any(param.strip().startswith(kw) for kw in type_keys):
849 self.template_arguments.append(CxxClass(arg))
850 else:
851 self.template_arguments.append(arg)
852
853 def declare(self, code):
854 # First declare any template argument types.
855 for arg in self.template_arguments:
856 if isinstance(arg, CxxClass):
857 arg.declare(code)
858 # Re-open the target namespace.
859 for ns in self.namespaces:
860 code('namespace $ns {')
861 # If this is a class template...
862 if self.template_params:
863 code('template <${{", ".join(self.template_params)}}>')
864 # The actual class declaration.
865 code('class ${{self.name}};')
866 # Close the target namespaces.
867 for ns in reversed(self.namespaces):
868 code('} // namespace $ns')
869
870 code('''\
871#ifndef __PARAMS__${cls}__
872#define __PARAMS__${cls}__
873
874''')
875
876
877 # The base SimObject has a couple of params that get
878 # automatically set from Python without being declared through
879 # the normal Param mechanism; we slip them in here (needed
880 # predecls now, actual declarations below)
881 if cls == SimObject:
882 code('''#include <string>''')
883
884 cxx_class = CxxClass(cls._value_dict['cxx_class'],
885 cls._value_dict['cxx_template_params'])
886
887 # A forward class declaration is sufficient since we are just
888 # declaring a pointer.
889 cxx_class.declare(code)
890
891 for param in params:
892 param.cxx_predecls(code)
893 for port in ports.values():
894 port.cxx_predecls(code)
895 code()
896
897 if cls._base:
898 code('#include "params/${{cls._base.type}}.hh"')
899 code()
900
901 for ptype in ptypes:
902 if issubclass(ptype, Enum):
903 code('#include "enums/${{ptype.__name__}}.hh"')
904 code()
905
906 # now generate the actual param struct
907 code("struct ${cls}Params")
908 if cls._base:
909 code(" : public ${{cls._base.type}}Params")
910 code("{")
911 if not hasattr(cls, 'abstract') or not cls.abstract:
912 if 'type' in cls.__dict__:
913 code(" ${{cls.cxx_type}} create();")
914
915 code.indent()
916 if cls == SimObject:
917 code('''
918 SimObjectParams() {}
919 virtual ~SimObjectParams() {}
920
921 std::string name;
922 ''')
923
924 for param in params:
925 param.cxx_decl(code)
926 for port in ports.values():
927 port.cxx_decl(code)
928
929 code.dedent()
930 code('};')
931
932 code()
933 code('#endif // __PARAMS__${cls}__')
934 return code
935
936 # Generate the C++ declaration/definition files for this SimObject's
937 # param struct to allow C++ initialisation
938 def cxx_config_param_file(cls, code, is_header):
939 createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header)
940 return code
941
942# This *temporary* definition is required to support calls from the
943# SimObject class definition to the MetaSimObject methods (in
944# particular _set_param, which gets called for parameters with default
945# values defined on the SimObject class itself). It will get
946# overridden by the permanent definition (which requires that
947# SimObject be defined) lower in this file.
948def isSimObjectOrVector(value):
949 return False
950
951def cxxMethod(*args, **kwargs):
952 """Decorator to export C++ functions to Python"""
953
954 def decorate(func):
955 name = func.__name__
956 override = kwargs.get("override", False)
957 cxx_name = kwargs.get("cxx_name", name)
958 return_value_policy = kwargs.get("return_value_policy", None)
959
960 args, varargs, keywords, defaults = inspect.getargspec(func)
961 if varargs or keywords:
962 raise ValueError("Wrapped methods must not contain variable " \
963 "arguments")
964
965 # Create tuples of (argument, default)
966 if defaults:
967 args = args[:-len(defaults)] + \
968 list(zip(args[-len(defaults):], defaults))
969 # Don't include self in the argument list to PyBind
970 args = args[1:]
971
972
973 @wraps(func)
974 def cxx_call(self, *args, **kwargs):
975 ccobj = self.getCCObject()
976 return getattr(ccobj, name)(*args, **kwargs)
977
978 @wraps(func)
979 def py_call(self, *args, **kwargs):
980 return func(self, *args, **kwargs)
981
982 f = py_call if override else cxx_call
983 f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args,
984 return_value_policy=return_value_policy)
985
986 return f
987
988 if len(args) == 0:
989 return decorate
990 elif len(args) == 1 and len(kwargs) == 0:
991 return decorate(*args)
992 else:
993 raise TypeError("One argument and no kwargs, or only kwargs expected")
994
995# This class holds information about each simobject parameter
996# that should be displayed on the command line for use in the
997# configuration system.
998class ParamInfo(object):
999 def __init__(self, type, desc, type_str, example, default_val, access_str):
1000 self.type = type
1001 self.desc = desc
1002 self.type_str = type_str
1003 self.example_str = example
1004 self.default_val = default_val
1005 # The string representation used to access this param through python.
1006 # The method to access this parameter presented on the command line may
1007 # be different, so this needs to be stored for later use.
1008 self.access_str = access_str
1009 self.created = True
1010
1011 # Make it so we can only set attributes at initialization time
1012 # and effectively make this a const object.
1013 def __setattr__(self, name, value):
1014 if not "created" in self.__dict__:
1015 self.__dict__[name] = value
1016
1017class SimObjectCliWrapperException(Exception):
1018 def __init__(self, message):
1019 super(Exception, self).__init__(message)
1020
1021class SimObjectCliWrapper(object):
1022 """
1023 Wrapper class to restrict operations that may be done
1024 from the command line on SimObjects.
1025
1026 Only parameters may be set, and only children may be accessed.
1027
1028 Slicing allows for multiple simultaneous assignment of items in
1029 one statement.
1030 """
1031
1032 def __init__(self, sim_objects):
1033 self.__dict__['_sim_objects'] = list(sim_objects)
1034
1035 def __getattr__(self, key):
1036 return SimObjectCliWrapper(sim_object._children[key]
1037 for sim_object in self._sim_objects)
1038
1039 def __setattr__(self, key, val):
1040 for sim_object in self._sim_objects:
1041 if key in sim_object._params:
1042 if sim_object._params[key].isCmdLineSettable():
1043 setattr(sim_object, key, val)
1044 else:
1045 raise SimObjectCliWrapperException(
1046 'tried to set or unsettable' \
1047 'object parameter: ' + key)
1048 else:
1049 raise SimObjectCliWrapperException(
1050 'tried to set or access non-existent' \
1051 'object parameter: ' + key)
1052
1053 def __getitem__(self, idx):
1054 """
1055 Extends the list() semantics to also allow tuples,
1056 for example object[1, 3] selects items 1 and 3.
1057 """
1058 out = []
1059 if isinstance(idx, tuple):
1060 for t in idx:
1061 out.extend(self[t]._sim_objects)
1062 else:
1063 if isinstance(idx, int):
1064 _range = range(idx, idx + 1)
1065 elif not isinstance(idx, slice):
1066 raise SimObjectCliWrapperException( \
1067 'invalid index type: ' + repr(idx))
1068 for sim_object in self._sim_objects:
1069 if isinstance(idx, slice):
1070 _range = range(*idx.indices(len(sim_object)))
1071 out.extend(sim_object[i] for i in _range)
1072 return SimObjectCliWrapper(out)
1073
1074# The SimObject class is the root of the special hierarchy. Most of
1075# the code in this class deals with the configuration hierarchy itself
1076# (parent/child node relationships).
1077class SimObject(object):
1078 # Specify metaclass. Any class inheriting from SimObject will
1079 # get this metaclass.
1080 __metaclass__ = MetaSimObject
1081 type = 'SimObject'
1082 abstract = True
1083
1084 cxx_header = "sim/sim_object.hh"
1085 cxx_extra_bases = [ "Drainable", "Serializable" ]
1086 eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
1087
1088 cxx_exports = [
1089 PyBindMethod("init"),
1090 PyBindMethod("initState"),
1091 PyBindMethod("memInvalidate"),
1092 PyBindMethod("memWriteback"),
1093 PyBindMethod("regStats"),
1094 PyBindMethod("resetStats"),
1095 PyBindMethod("regProbePoints"),
1096 PyBindMethod("regProbeListeners"),
1097 PyBindMethod("startup"),
1098 ]
1099
1100 cxx_param_exports = [
1101 PyBindProperty("name"),
1102 ]
1103
1104 @cxxMethod
1105 def loadState(self, cp):
1106 """Load SimObject state from a checkpoint"""
1107 pass
1108
1109 # Returns a dict of all the option strings that can be
1110 # generated as command line options for this simobject instance
1111 # by tracing all reachable params in the top level instance and
1112 # any children it contains.
1113 def enumerateParams(self, flags_dict = {},
1114 cmd_line_str = "", access_str = ""):
1115 if hasattr(self, "_paramEnumed"):
1116 print("Cycle detected enumerating params")
1117 else:
1118 self._paramEnumed = True
1119 # Scan the children first to pick up all the objects in this SimObj
1120 for keys in self._children:
1121 child = self._children[keys]
1122 next_cmdline_str = cmd_line_str + keys
1123 next_access_str = access_str + keys
1124 if not isSimObjectVector(child):
1125 next_cmdline_str = next_cmdline_str + "."
1126 next_access_str = next_access_str + "."
1127 flags_dict = child.enumerateParams(flags_dict,
1128 next_cmdline_str,
1129 next_access_str)
1130
1131 # Go through the simple params in the simobject in this level
1132 # of the simobject hierarchy and save information about the
1133 # parameter to be used for generating and processing command line
1134 # options to the simulator to set these parameters.
1135 for keys,values in self._params.items():
1136 if values.isCmdLineSettable():
1137 type_str = ''
1138 ex_str = values.example_str()
1139 ptype = None
1140 if isinstance(values, VectorParamDesc):
1141 type_str = 'Vector_%s' % values.ptype_str
1142 ptype = values
1143 else:
1144 type_str = '%s' % values.ptype_str
1145 ptype = values.ptype
1146
1147 if keys in self._hr_values\
1148 and keys in self._values\
1149 and not isinstance(self._values[keys],
1150 m5.proxy.BaseProxy):
1151 cmd_str = cmd_line_str + keys
1152 acc_str = access_str + keys
1153 flags_dict[cmd_str] = ParamInfo(ptype,
1154 self._params[keys].desc, type_str, ex_str,
1155 values.pretty_print(self._hr_values[keys]),
1156 acc_str)
1157 elif not keys in self._hr_values\
1158 and not keys in self._values:
1159 # Empty param
1160 cmd_str = cmd_line_str + keys
1161 acc_str = access_str + keys
1162 flags_dict[cmd_str] = ParamInfo(ptype,
1163 self._params[keys].desc,
1164 type_str, ex_str, '', acc_str)
1165
1166 return flags_dict
1167
1168 # Initialize new instance. For objects with SimObject-valued
1169 # children, we need to recursively clone the classes represented
1170 # by those param values as well in a consistent "deep copy"-style
1171 # fashion. That is, we want to make sure that each instance is
1172 # cloned only once, and that if there are multiple references to
1173 # the same original object, we end up with the corresponding
1174 # cloned references all pointing to the same cloned instance.
1175 def __init__(self, **kwargs):
1176 ancestor = kwargs.get('_ancestor')
1177 memo_dict = kwargs.get('_memo')
1178 if memo_dict is None:
1179 # prepare to memoize any recursively instantiated objects
1180 memo_dict = {}
1181 elif ancestor:
1182 # memoize me now to avoid problems with recursive calls
1183 memo_dict[ancestor] = self
1184
1185 if not ancestor:
1186 ancestor = self.__class__
1187 ancestor._instantiated = True
1188
1189 # initialize required attributes
1190 self._parent = None
1191 self._name = None
1192 self._ccObject = None # pointer to C++ object
1193 self._ccParams = None
1194 self._instantiated = False # really "cloned"
1195
1196 # Clone children specified at class level. No need for a
1197 # multidict here since we will be cloning everything.
1198 # Do children before parameter values so that children that
1199 # are also param values get cloned properly.
1200 self._children = {}
1201 for key,val in ancestor._children.items():
1202 self.add_child(key, val(_memo=memo_dict))
1203
1204 # Inherit parameter values from class using multidict so
1205 # individual value settings can be overridden but we still
1206 # inherit late changes to non-overridden class values.
1207 self._values = multidict(ancestor._values)
1208 self._hr_values = multidict(ancestor._hr_values)
1209 # clone SimObject-valued parameters
1210 for key,val in ancestor._values.items():
1211 val = tryAsSimObjectOrVector(val)
1212 if val is not None:
1213 self._values[key] = val(_memo=memo_dict)
1214
1215 # clone port references. no need to use a multidict here
1216 # since we will be creating new references for all ports.
1217 self._port_refs = {}
1218 for key,val in ancestor._port_refs.items():
1219 self._port_refs[key] = val.clone(self, memo_dict)
1220 # apply attribute assignments from keyword args, if any
1221 for key,val in kwargs.items():
1222 setattr(self, key, val)
1223
1224 # "Clone" the current instance by creating another instance of
1225 # this instance's class, but that inherits its parameter values
1226 # and port mappings from the current instance. If we're in a
1227 # "deep copy" recursive clone, check the _memo dict to see if
1228 # we've already cloned this instance.
1229 def __call__(self, **kwargs):
1230 memo_dict = kwargs.get('_memo')
1231 if memo_dict is None:
1232 # no memo_dict: must be top-level clone operation.
1233 # this is only allowed at the root of a hierarchy
1234 if self._parent:
1235 raise RuntimeError("attempt to clone object %s " \
1236 "not at the root of a tree (parent = %s)" \
1237 % (self, self._parent))
1238 # create a new dict and use that.
1239 memo_dict = {}
1240 kwargs['_memo'] = memo_dict
1241 elif self in memo_dict:
1242 # clone already done & memoized
1243 return memo_dict[self]
1244 return self.__class__(_ancestor = self, **kwargs)
1245
1246 def _get_port_ref(self, attr):
1247 # Return reference that can be assigned to another port
1248 # via __setattr__. There is only ever one reference
1249 # object per port, but we create them lazily here.
1250 ref = self._port_refs.get(attr)
1251 if ref == None:
1252 ref = self._ports[attr].makeRef(self)
1253 self._port_refs[attr] = ref
1254 return ref
1255
1256 def __getattr__(self, attr):
1257 if attr in self._ports:
1258 return self._get_port_ref(attr)
1259
1260 if attr in self._values:
1261 return self._values[attr]
1262
1263 if attr in self._children:
1264 return self._children[attr]
1265
1266 # If the attribute exists on the C++ object, transparently
1267 # forward the reference there. This is typically used for
1268 # methods exported to Python (e.g., init(), and startup())
1269 if self._ccObject and hasattr(self._ccObject, attr):
1270 return getattr(self._ccObject, attr)
1271
1272 err_string = "object '%s' has no attribute '%s'" \
1273 % (self.__class__.__name__, attr)
1274
1275 if not self._ccObject:
1276 err_string += "\n (C++ object is not yet constructed," \
1277 " so wrapped C++ methods are unavailable.)"
1278
1279 raise AttributeError(err_string)
1280
1281 # Set attribute (called on foo.attr = value when foo is an
1282 # instance of class cls).
1283 def __setattr__(self, attr, value):
1284 # normal processing for private attributes
1285 if attr.startswith('_'):
1286 object.__setattr__(self, attr, value)
1287 return
1288
1289 if attr in self._ports:
1290 # set up port connection
1291 self._get_port_ref(attr).connect(value)
1292 return
1293
1294 param = self._params.get(attr)
1295 if param:
1296 try:
1297 hr_value = value
1298 value = param.convert(value)
1299 except Exception as e:
1300 msg = "%s\nError setting param %s.%s to %s\n" % \
1301 (e, self.__class__.__name__, attr, value)
1302 e.args = (msg, )
1303 raise
1304 self._values[attr] = value
1305 # implicitly parent unparented objects assigned as params
1306 if isSimObjectOrVector(value) and not value.has_parent():
1307 self.add_child(attr, value)
1308 # set the human-readable value dict if this is a param
1309 # with a literal value and is not being set as an object
1310 # or proxy.
1311 if not (isSimObjectOrVector(value) or\
1312 isinstance(value, m5.proxy.BaseProxy)):
1313 self._hr_values[attr] = hr_value
1314
1315 return
1316
1317 # if RHS is a SimObject, it's an implicit child assignment
1318 if isSimObjectOrSequence(value):
1319 self.add_child(attr, value)
1320 return
1321
1322 # no valid assignment... raise exception
1323 raise AttributeError("Class %s has no parameter %s" \
1324 % (self.__class__.__name__, attr))
1325
1326
1327 # this hack allows tacking a '[0]' onto parameters that may or may
1328 # not be vectors, and always getting the first element (e.g. cpus)
1329 def __getitem__(self, key):
1330 if key == 0:
1331 return self
1332 raise IndexError("Non-zero index '%s' to SimObject" % key)
1333
1334 # this hack allows us to iterate over a SimObject that may
1335 # not be a vector, so we can call a loop over it and get just one
1336 # element.
1337 def __len__(self):
1338 return 1
1339
1340 # Also implemented by SimObjectVector
1341 def clear_parent(self, old_parent):
1342 assert self._parent is old_parent
1343 self._parent = None
1344
1345 # Also implemented by SimObjectVector
1346 def set_parent(self, parent, name):
1347 self._parent = parent
1348 self._name = name
1349
1350 # Return parent object of this SimObject, not implemented by
1351 # SimObjectVector because the elements in a SimObjectVector may not share
1352 # the same parent
1353 def get_parent(self):
1354 return self._parent
1355
1356 # Also implemented by SimObjectVector
1357 def get_name(self):
1358 return self._name
1359
1360 # Also implemented by SimObjectVector
1361 def has_parent(self):
1362 return self._parent is not None
1363
1364 # clear out child with given name. This code is not likely to be exercised.
1365 # See comment in add_child.
1366 def clear_child(self, name):
1367 child = self._children[name]
1368 child.clear_parent(self)
1369 del self._children[name]
1370
1371 # Add a new child to this object.
1372 def add_child(self, name, child):
1373 child = coerceSimObjectOrVector(child)
1374 if child.has_parent():
1375 warn("add_child('%s'): child '%s' already has parent", name,
1376 child.get_name())
1377 if name in self._children:
1378 # This code path had an undiscovered bug that would make it fail
1379 # at runtime. It had been here for a long time and was only
1380 # exposed by a buggy script. Changes here will probably not be
1381 # exercised without specialized testing.
1382 self.clear_child(name)
1383 child.set_parent(self, name)
1384 if not isNullPointer(child):
1385 self._children[name] = child
1386
1387 # Take SimObject-valued parameters that haven't been explicitly
1388 # assigned as children and make them children of the object that
1389 # they were assigned to as a parameter value. This guarantees
1390 # that when we instantiate all the parameter objects we're still
1391 # inside the configuration hierarchy.
1392 def adoptOrphanParams(self):
1393 for key,val in self._values.items():
1394 if not isSimObjectVector(val) and isSimObjectSequence(val):
1395 # need to convert raw SimObject sequences to
1396 # SimObjectVector class so we can call has_parent()
1397 val = SimObjectVector(val)
1398 self._values[key] = val
1399 if isSimObjectOrVector(val) and not val.has_parent():
1400 warn("%s adopting orphan SimObject param '%s'", self, key)
1401 self.add_child(key, val)
1402
1403 def path(self):
1404 if not self._parent:
1405 return '<orphan %s>' % self.__class__
1406 elif isinstance(self._parent, MetaSimObject):
1407 return str(self.__class__)
1408
1409 ppath = self._parent.path()
1410 if ppath == 'root':
1411 return self._name
1412 return ppath + "." + self._name
1413
1414 def __str__(self):
1415 return self.path()
1416
1417 def config_value(self):
1418 return self.path()
1419
1420 def ini_str(self):
1421 return self.path()
1422
1423 def find_any(self, ptype):
1424 if isinstance(self, ptype):
1425 return self, True
1426
1427 found_obj = None
1428 for child in self._children.values():
1429 visited = False
1430 if hasattr(child, '_visited'):
1431 visited = getattr(child, '_visited')
1432
1433 if isinstance(child, ptype) and not visited:
1434 if found_obj != None and child != found_obj:
1435 raise AttributeError(
1436 'parent.any matched more than one: %s %s' % \
1437 (found_obj.path, child.path))
1438 found_obj = child
1439 # search param space
1440 for pname,pdesc in self._params.items():
1441 if issubclass(pdesc.ptype, ptype):
1442 match_obj = self._values[pname]
1443 if found_obj != None and found_obj != match_obj:
1444 raise AttributeError(
1445 'parent.any matched more than one: %s and %s' % \
1446 (found_obj.path, match_obj.path))
1447 found_obj = match_obj
1448 return found_obj, found_obj != None
1449
1450 def find_all(self, ptype):
1451 all = {}
1452 # search children
1453 for child in self._children.values():
1454 # a child could be a list, so ensure we visit each item
1455 if isinstance(child, list):
1456 children = child
1457 else:
1458 children = [child]
1459
1460 for child in children:
1461 if isinstance(child, ptype) and not isproxy(child) and \
1462 not isNullPointer(child):
1463 all[child] = True
1464 if isSimObject(child):
1465 # also add results from the child itself
1466 child_all, done = child.find_all(ptype)
1467 all.update(dict(zip(child_all, [done] * len(child_all))))
1468 # search param space
1469 for pname,pdesc in self._params.items():
1470 if issubclass(pdesc.ptype, ptype):
1471 match_obj = self._values[pname]
1472 if not isproxy(match_obj) and not isNullPointer(match_obj):
1473 all[match_obj] = True
1474 # Also make sure to sort the keys based on the objects' path to
1475 # ensure that the order is the same on all hosts
1476 return sorted(all.keys(), key = lambda o: o.path()), True
1477
1478 def unproxy(self, base):
1479 return self
1480
1481 def unproxyParams(self):
1482 for param in self._params.keys():
1483 value = self._values.get(param)
1484 if value != None and isproxy(value):
1485 try:
1486 value = value.unproxy(self)
1487 except:
1488 print("Error in unproxying param '%s' of %s" %
1489 (param, self.path()))
1490 raise
1491 setattr(self, param, value)
1492
1493 # Unproxy ports in sorted order so that 'append' operations on
1494 # vector ports are done in a deterministic fashion.
1495 port_names = list(self._ports.keys())
1496 port_names.sort()
1497 for port_name in port_names:
1498 port = self._port_refs.get(port_name)
1499 if port != None:
1500 port.unproxy(self)
1501
1502 def print_ini(self, ini_file):
1503 print('[' + self.path() + ']', file=ini_file) # .ini section header
1504
1505 instanceDict[self.path()] = self
1506
1507 if hasattr(self, 'type'):
1508 print('type=%s' % self.type, file=ini_file)
1509
1510 if len(self._children.keys()):
1511 print('children=%s' %
1512 ' '.join(self._children[n].get_name()
1513 for n in sorted(self._children.keys())),
1514 file=ini_file)
1515
1516 for param in sorted(self._params.keys()):
1517 value = self._values.get(param)
1518 if value != None:
1519 print('%s=%s' % (param, self._values[param].ini_str()),
1520 file=ini_file)
1521
1522 for port_name in sorted(self._ports.keys()):
1523 port = self._port_refs.get(port_name, None)
1524 if port != None:
1525 print('%s=%s' % (port_name, port.ini_str()), file=ini_file)
1526
1527 print(file=ini_file) # blank line between objects
1528
1529 # generate a tree of dictionaries expressing all the parameters in the
1530 # instantiated system for use by scripts that want to do power, thermal
1531 # visualization, and other similar tasks
1532 def get_config_as_dict(self):
1533 d = attrdict()
1534 if hasattr(self, 'type'):
1535 d.type = self.type
1536 if hasattr(self, 'cxx_class'):
1537 d.cxx_class = self.cxx_class
1538 # Add the name and path of this object to be able to link to
1539 # the stats
1540 d.name = self.get_name()
1541 d.path = self.path()
1542
1543 for param in sorted(self._params.keys()):
1544 value = self._values.get(param)
1545 if value != None:
1546 d[param] = value.config_value()
1547
1548 for n in sorted(self._children.keys()):
1549 child = self._children[n]
1550 # Use the name of the attribute (and not get_name()) as
1551 # the key in the JSON dictionary to capture the hierarchy
1552 # in the Python code that assembled this system
1553 d[n] = child.get_config_as_dict()
1554
1555 for port_name in sorted(self._ports.keys()):
1556 port = self._port_refs.get(port_name, None)
1557 if port != None:
1558 # Represent each port with a dictionary containing the
1559 # prominent attributes
1560 d[port_name] = port.get_config_as_dict()
1561
1562 return d
1563
1564 def getCCParams(self):
1565 if self._ccParams:
1566 return self._ccParams
1567
1568 cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type)
1569 cc_params = cc_params_struct()
1570 cc_params.name = str(self)
1571
1572 param_names = list(self._params.keys())
1573 param_names.sort()
1574 for param in param_names:
1575 value = self._values.get(param)
1576 if value is None:
1577 fatal("%s.%s without default or user set value",
1578 self.path(), param)
1579
1580 value = value.getValue()
1581 if isinstance(self._params[param], VectorParamDesc):
1582 assert isinstance(value, list)
1583 vec = getattr(cc_params, param)
1584 assert not len(vec)
1585 # Some types are exposed as opaque types. They support
1586 # the append operation unlike the automatically
1587 # wrapped types.
1588 if isinstance(vec, list):
1589 setattr(cc_params, param, list(value))
1590 else:
1591 for v in value:
1592 getattr(cc_params, param).append(v)
1593 else:
1594 setattr(cc_params, param, value)
1595
1596 port_names = list(self._ports.keys())
1597 port_names.sort()
1598 for port_name in port_names:
1599 port = self._port_refs.get(port_name, None)
1600 if port != None:
1601 port_count = len(port)
1602 else:
1603 port_count = 0
1604 setattr(cc_params, 'port_' + port_name + '_connection_count',
1605 port_count)
1606 self._ccParams = cc_params
1607 return self._ccParams
1608
1609 # Get C++ object corresponding to this object, calling C++ if
1610 # necessary to construct it. Does *not* recursively create
1611 # children.
1612 def getCCObject(self):
1613 if not self._ccObject:
1614 # Make sure this object is in the configuration hierarchy
1615 if not self._parent and not isRoot(self):
1616 raise RuntimeError("Attempt to instantiate orphan node")
1617 # Cycles in the configuration hierarchy are not supported. This
1618 # will catch the resulting recursion and stop.
1619 self._ccObject = -1
1620 if not self.abstract:
1621 params = self.getCCParams()
1622 self._ccObject = params.create()
1623 elif self._ccObject == -1:
1624 raise RuntimeError("%s: Cycle found in configuration hierarchy." \
1625 % self.path())
1626 return self._ccObject
1627
1628 def descendants(self):
1629 yield self
1630 # The order of the dict is implementation dependent, so sort
1631 # it based on the key (name) to ensure the order is the same
1632 # on all hosts
1633 for (name, child) in sorted(self._children.items()):
1634 for obj in child.descendants():
1635 yield obj
1636
1637 # Call C++ to create C++ object corresponding to this object
1638 def createCCObject(self):
1639 self.getCCParams()
1640 self.getCCObject() # force creation
1641
1642 def getValue(self):
1643 return self.getCCObject()
1644
1645 @cxxMethod(return_value_policy="reference")
1646 def getPort(self, if_name, idx):
1647 pass
1648
1645 # Create C++ port connections corresponding to the connections in
1646 # _port_refs
1647 def connectPorts(self):
1648 # Sort the ports based on their attribute name to ensure the
1649 # order is the same on all hosts
1650 for (attr, portRef) in sorted(self._port_refs.items()):
1651 portRef.ccConnect()
1652
1653 # Default function for generating the device structure.
1654 # Can be overloaded by the inheriting class
1655 def generateDeviceTree(self, state):
1656 return # return without yielding anything
1657 yield # make this function a (null) generator
1658
1659 def recurseDeviceTree(self, state):
1660 for child in self._children.values():
1661 for item in child: # For looping over SimObjectVectors
1662 for dt in item.generateDeviceTree(state):
1663 yield dt
1664
1665 # On a separate method otherwise certain buggy Python versions
1666 # would fail with: SyntaxError: unqualified exec is not allowed
1667 # in function 'apply_config'
1668 def _apply_config_get_dict(self):
1669 return {
1670 child_name: SimObjectCliWrapper(
1671 iter(self._children[child_name]))
1672 for child_name in self._children
1673 }
1674
1675 def apply_config(self, params):
1676 """
1677 exec a list of Python code strings contained in params.
1678
1679 The only exposed globals to those strings are the child
1680 SimObjects of this node.
1681
1682 This function is intended to allow users to modify SimObject
1683 parameters from the command line with Python statements.
1684 """
1685 d = self._apply_config_get_dict()
1686 for param in params:
1687 exec(param, d)
1688
1689# Function to provide to C++ so it can look up instances based on paths
1690def resolveSimObject(name):
1691 obj = instanceDict[name]
1692 return obj.getCCObject()
1693
1694def isSimObject(value):
1695 return isinstance(value, SimObject)
1696
1697def isSimObjectClass(value):
1698 return issubclass(value, SimObject)
1699
1700def isSimObjectVector(value):
1701 return isinstance(value, SimObjectVector)
1702
1703def isSimObjectSequence(value):
1704 if not isinstance(value, (list, tuple)) or len(value) == 0:
1705 return False
1706
1707 for val in value:
1708 if not isNullPointer(val) and not isSimObject(val):
1709 return False
1710
1711 return True
1712
1713def isSimObjectOrSequence(value):
1714 return isSimObject(value) or isSimObjectSequence(value)
1715
1716def isRoot(obj):
1717 from m5.objects import Root
1718 return obj and obj is Root.getInstance()
1719
1720def isSimObjectOrVector(value):
1721 return isSimObject(value) or isSimObjectVector(value)
1722
1723def tryAsSimObjectOrVector(value):
1724 if isSimObjectOrVector(value):
1725 return value
1726 if isSimObjectSequence(value):
1727 return SimObjectVector(value)
1728 return None
1729
1730def coerceSimObjectOrVector(value):
1731 value = tryAsSimObjectOrVector(value)
1732 if value is None:
1733 raise TypeError("SimObject or SimObjectVector expected")
1734 return value
1735
1736baseClasses = allClasses.copy()
1737baseInstances = instanceDict.copy()
1738
1739def clear():
1740 global allClasses, instanceDict, noCxxHeader
1741
1742 allClasses = baseClasses.copy()
1743 instanceDict = baseInstances.copy()
1744 noCxxHeader = False
1745
1746# __all__ defines the list of symbols that get exported when
1747# 'from config import *' is invoked. Try to keep this reasonably
1748# short to avoid polluting other namespaces.
1749__all__ = [
1750 'SimObject',
1751 'cxxMethod',
1752 'PyBindMethod',
1753 'PyBindProperty',
1754]
1649 # Create C++ port connections corresponding to the connections in
1650 # _port_refs
1651 def connectPorts(self):
1652 # Sort the ports based on their attribute name to ensure the
1653 # order is the same on all hosts
1654 for (attr, portRef) in sorted(self._port_refs.items()):
1655 portRef.ccConnect()
1656
1657 # Default function for generating the device structure.
1658 # Can be overloaded by the inheriting class
1659 def generateDeviceTree(self, state):
1660 return # return without yielding anything
1661 yield # make this function a (null) generator
1662
1663 def recurseDeviceTree(self, state):
1664 for child in self._children.values():
1665 for item in child: # For looping over SimObjectVectors
1666 for dt in item.generateDeviceTree(state):
1667 yield dt
1668
1669 # On a separate method otherwise certain buggy Python versions
1670 # would fail with: SyntaxError: unqualified exec is not allowed
1671 # in function 'apply_config'
1672 def _apply_config_get_dict(self):
1673 return {
1674 child_name: SimObjectCliWrapper(
1675 iter(self._children[child_name]))
1676 for child_name in self._children
1677 }
1678
1679 def apply_config(self, params):
1680 """
1681 exec a list of Python code strings contained in params.
1682
1683 The only exposed globals to those strings are the child
1684 SimObjects of this node.
1685
1686 This function is intended to allow users to modify SimObject
1687 parameters from the command line with Python statements.
1688 """
1689 d = self._apply_config_get_dict()
1690 for param in params:
1691 exec(param, d)
1692
1693# Function to provide to C++ so it can look up instances based on paths
1694def resolveSimObject(name):
1695 obj = instanceDict[name]
1696 return obj.getCCObject()
1697
1698def isSimObject(value):
1699 return isinstance(value, SimObject)
1700
1701def isSimObjectClass(value):
1702 return issubclass(value, SimObject)
1703
1704def isSimObjectVector(value):
1705 return isinstance(value, SimObjectVector)
1706
1707def isSimObjectSequence(value):
1708 if not isinstance(value, (list, tuple)) or len(value) == 0:
1709 return False
1710
1711 for val in value:
1712 if not isNullPointer(val) and not isSimObject(val):
1713 return False
1714
1715 return True
1716
1717def isSimObjectOrSequence(value):
1718 return isSimObject(value) or isSimObjectSequence(value)
1719
1720def isRoot(obj):
1721 from m5.objects import Root
1722 return obj and obj is Root.getInstance()
1723
1724def isSimObjectOrVector(value):
1725 return isSimObject(value) or isSimObjectVector(value)
1726
1727def tryAsSimObjectOrVector(value):
1728 if isSimObjectOrVector(value):
1729 return value
1730 if isSimObjectSequence(value):
1731 return SimObjectVector(value)
1732 return None
1733
1734def coerceSimObjectOrVector(value):
1735 value = tryAsSimObjectOrVector(value)
1736 if value is None:
1737 raise TypeError("SimObject or SimObjectVector expected")
1738 return value
1739
1740baseClasses = allClasses.copy()
1741baseInstances = instanceDict.copy()
1742
1743def clear():
1744 global allClasses, instanceDict, noCxxHeader
1745
1746 allClasses = baseClasses.copy()
1747 instanceDict = baseInstances.copy()
1748 noCxxHeader = False
1749
1750# __all__ defines the list of symbols that get exported when
1751# 'from config import *' is invoked. Try to keep this reasonably
1752# short to avoid polluting other namespaces.
1753__all__ = [
1754 'SimObject',
1755 'cxxMethod',
1756 'PyBindMethod',
1757 'PyBindProperty',
1758]