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