SimObject.py revision 1590
1# Copyright (c) 2004 The Regents of The University of Michigan 2# All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions are 6# met: redistributions of source code must retain the above copyright 7# notice, this list of conditions and the following disclaimer; 8# redistributions in binary form must reproduce the above copyright 9# notice, this list of conditions and the following disclaimer in the 10# documentation and/or other materials provided with the distribution; 11# neither the name of the copyright holders nor the names of its 12# contributors may be used to endorse or promote products derived from 13# this software without specific prior written permission. 14# 15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 27from __future__ import generators 28import os, re, sys, types, inspect 29 30from m5 import panic 31from convert import * 32from multidict import multidict 33 34noDot = False 35try: 36 import pydot 37except: 38 noDot = True 39 40class Singleton(type): 41 def __call__(cls, *args, **kwargs): 42 if hasattr(cls, '_instance'): 43 return cls._instance 44 45 cls._instance = super(Singleton, cls).__call__(*args, **kwargs) 46 return cls._instance 47 48##################################################################### 49# 50# M5 Python Configuration Utility 51# 52# The basic idea is to write simple Python programs that build Python 53# objects corresponding to M5 SimObjects for the deisred simulation 54# configuration. For now, the Python emits a .ini file that can be 55# parsed by M5. In the future, some tighter integration between M5 56# and the Python interpreter may allow bypassing the .ini file. 57# 58# Each SimObject class in M5 is represented by a Python class with the 59# same name. The Python inheritance tree mirrors the M5 C++ tree 60# (e.g., SimpleCPU derives from BaseCPU in both cases, and all 61# SimObjects inherit from a single SimObject base class). To specify 62# an instance of an M5 SimObject in a configuration, the user simply 63# instantiates the corresponding Python object. The parameters for 64# that SimObject are given by assigning to attributes of the Python 65# object, either using keyword assignment in the constructor or in 66# separate assignment statements. For example: 67# 68# cache = BaseCache('my_cache', root, size='64KB') 69# cache.hit_latency = 3 70# cache.assoc = 8 71# 72# (The first two constructor arguments specify the name of the created 73# cache and its parent node in the hierarchy.) 74# 75# The magic lies in the mapping of the Python attributes for SimObject 76# classes to the actual SimObject parameter specifications. This 77# allows parameter validity checking in the Python code. Continuing 78# the example above, the statements "cache.blurfl=3" or 79# "cache.assoc='hello'" would both result in runtime errors in Python, 80# since the BaseCache object has no 'blurfl' parameter and the 'assoc' 81# parameter requires an integer, respectively. This magic is done 82# primarily by overriding the special __setattr__ method that controls 83# assignment to object attributes. 84# 85# The Python module provides another class, ConfigNode, which is a 86# superclass of SimObject. ConfigNode implements the parent/child 87# relationship for building the configuration hierarchy tree. 88# Concrete instances of ConfigNode can be used to group objects in the 89# hierarchy, but do not correspond to SimObjects themselves (like a 90# .ini section with "children=" but no "type=". 91# 92# Once a set of Python objects have been instantiated in a hierarchy, 93# calling 'instantiate(obj)' (where obj is the root of the hierarchy) 94# will generate a .ini file. See simple-4cpu.py for an example 95# (corresponding to m5-test/simple-4cpu.ini). 96# 97##################################################################### 98 99##################################################################### 100# 101# ConfigNode/SimObject classes 102# 103# The Python class hierarchy rooted by ConfigNode (which is the base 104# class of SimObject, which in turn is the base class of all other M5 105# SimObject classes) has special attribute behavior. In general, an 106# object in this hierarchy has three categories of attribute-like 107# things: 108# 109# 1. Regular Python methods and variables. These must start with an 110# underscore to be treated normally. 111# 112# 2. SimObject parameters. These values are stored as normal Python 113# attributes, but all assignments to these attributes are checked 114# against the pre-defined set of parameters stored in the class's 115# _params dictionary. Assignments to attributes that do not 116# correspond to predefined parameters, or that are not of the correct 117# type, incur runtime errors. 118# 119# 3. Hierarchy children. The child nodes of a ConfigNode are stored 120# in the node's _children dictionary, but can be accessed using the 121# Python attribute dot-notation (just as they are printed out by the 122# simulator). Children cannot be created using attribute assigment; 123# they must be added by specifying the parent node in the child's 124# constructor or using the '+=' operator. 125 126# The SimObject parameters are the most complex, for a few reasons. 127# First, both parameter descriptions and parameter values are 128# inherited. Thus parameter description lookup must go up the 129# inheritance chain like normal attribute lookup, but this behavior 130# must be explicitly coded since the lookup occurs in each class's 131# _params attribute. Second, because parameter values can be set 132# on SimObject classes (to implement default values), the parameter 133# checking behavior must be enforced on class attribute assignments as 134# well as instance attribute assignments. Finally, because we allow 135# class specialization via inheritance (e.g., see the L1Cache class in 136# the simple-4cpu.py example), we must do parameter checking even on 137# class instantiation. To provide all these features, we use a 138# metaclass to define most of the SimObject parameter behavior for 139# this class hierarchy. 140# 141##################################################################### 142 143class Proxy(object): 144 def __init__(self, path): 145 self._object = None 146 if path == 'any': 147 self._path = None 148 else: 149 # path is a list of (attr,index) tuples 150 self._path = [(path,None)] 151 self._index = None 152 self._multiplier = None 153 154 def __getattr__(self, attr): 155 # python uses __bases__ internally for inheritance 156 if attr == '__bases__': 157 return super(Proxy, self).__getattr__(self, attr) 158 if (self._path == None): 159 panic("Can't add attributes to 'any' proxy") 160 self._path.append((attr,None)) 161 return self 162 163 def __setattr__(self, attr, value): 164 if not attr.startswith('_'): 165 raise AttributeError, 'cannot set attribute %s' % attr 166 super(Proxy, self).__setattr__(attr, value) 167 168 # support indexing on proxies (e.g., parent.cpu[0]) 169 def __getitem__(self, key): 170 if not isinstance(key, int): 171 raise TypeError, "Proxy object requires integer index" 172 if self._path == None: 173 raise IndexError, "Index applied to 'any' proxy" 174 # replace index portion of last path element with new index 175 self._path[-1] = (self._path[-1][0], key) 176 return self 177 178 # support multiplying proxies by constants 179 def __mul__(self, other): 180 if not isinstance(other, int): 181 raise TypeError, "Proxy multiplier must be integer" 182 if self._multiplier == None: 183 self._multiplier = other 184 else: 185 # support chained multipliers 186 self._multiplier *= other 187 return self 188 189 def _mulcheck(self, result): 190 if self._multiplier == None: 191 return result 192 if not isinstance(result, int): 193 raise TypeError, "Proxy with multiplier resolves to " \ 194 "non-integer value" 195 return result * self._multiplier 196 197 def unproxy(self, base, ptype): 198 obj = base 199 done = False 200 while not done: 201 if obj is None: 202 raise AttributeError, \ 203 'Parent of %s type %s not found at path %s' \ 204 % (base.name, ptype, self._path) 205 206 result, done = obj.find(ptype, self._path) 207 obj = obj.parent 208 209 if isinstance(result, Proxy): 210 result = result.unproxy(obj, ptype) 211 212 return self._mulcheck(result) 213 214 def getindex(obj, index): 215 if index == None: 216 return obj 217 try: 218 obj = obj[index] 219 except TypeError: 220 if index != 0: 221 raise 222 # if index is 0 and item is not subscriptable, just 223 # use item itself (so cpu[0] works on uniprocessors) 224 return obj 225 getindex = staticmethod(getindex) 226 227class ProxyFactory(object): 228 def __getattr__(self, attr): 229 return Proxy(attr) 230 231# global object for handling parent.foo proxies 232parent = ProxyFactory() 233 234def isSubClass(value, cls): 235 try: 236 return issubclass(value, cls) 237 except: 238 return False 239 240def isConfigNode(value): 241 try: 242 return issubclass(value, ConfigNode) 243 except: 244 return False 245 246def isSimObject(value): 247 try: 248 return issubclass(value, SimObject) 249 except: 250 return False 251 252def isSimObjSequence(value): 253 if not isinstance(value, (list, tuple)): 254 return False 255 256 for val in value: 257 if not isNullPointer(val) and not isConfigNode(val): 258 return False 259 260 return True 261 262def isParamContext(value): 263 try: 264 return issubclass(value, ParamContext) 265 except: 266 return False 267 268 269class_decorator = 'M5M5_SIMOBJECT_' 270expr_decorator = 'M5M5_EXPRESSION_' 271dot_decorator = '_M5M5_DOT_' 272 273# 'Global' map of legitimate types for SimObject parameters. 274param_types = {} 275 276# Dummy base class to identify types that are legitimate for SimObject 277# parameters. 278class ParamType(object): 279 pass 280 281# Add types defined in given context (dict or module) that are derived 282# from ParamType to param_types map. 283def add_param_types(ctx): 284 if isinstance(ctx, types.DictType): 285 source_dict = ctx 286 elif isinstance(ctx, types.ModuleType): 287 source_dict = ctx.__dict__ 288 else: 289 raise TypeError, \ 290 "m5.config.add_param_types requires dict or module as arg" 291 for key,val in source_dict.iteritems(): 292 if isinstance(val, type) and issubclass(val, ParamType): 293 param_types[key] = val 294 295# The metaclass for ConfigNode (and thus for everything that derives 296# from ConfigNode, including SimObject). This class controls how new 297# classes that derive from ConfigNode are instantiated, and provides 298# inherited class behavior (just like a class controls how instances 299# of that class are instantiated, and provides inherited instance 300# behavior). 301class MetaConfigNode(type): 302 # Attributes that can be set only at initialization time 303 init_keywords = {} 304 # Attributes that can be set any time 305 keywords = { 'check' : types.FunctionType, 306 'children' : types.ListType } 307 308 # __new__ is called before __init__, and is where the statements 309 # in the body of the class definition get loaded into the class's 310 # __dict__. We intercept this to filter out parameter assignments 311 # and only allow "private" attributes to be passed to the base 312 # __new__ (starting with underscore). 313 def __new__(mcls, name, bases, dict): 314 # Copy "private" attributes (including special methods such as __new__) 315 # to the official dict. Everything else goes in _init_dict to be 316 # filtered in __init__. 317 cls_dict = {} 318 for key,val in dict.items(): 319 if key.startswith('_'): 320 cls_dict[key] = val 321 del dict[key] 322 cls_dict['_init_dict'] = dict 323 return super(MetaConfigNode, mcls).__new__(mcls, name, bases, cls_dict) 324 325 # initialization 326 def __init__(cls, name, bases, dict): 327 super(MetaConfigNode, cls).__init__(name, bases, dict) 328 329 # initialize required attributes 330 cls._params = multidict() 331 cls._values = multidict() 332 cls._param_types = {} 333 cls._bases = [c for c in cls.__mro__ if isConfigNode(c)] 334 cls._anon_subclass_counter = 0 335 336 # We don't support multiple inheritence. If you want to, you 337 # must fix multidict to deal with it properly. 338 cnbase = [ base for base in bases if isConfigNode(base) ] 339 if len(cnbase) == 1: 340 # If your parent has a value in it that's a config node, clone 341 # it. Do this now so if we update any of the values' 342 # attributes we are updating the clone and not the original. 343 for key,val in cnbase[0]._values.iteritems(): 344 345 # don't clone if (1) we're about to overwrite it with 346 # a local setting or (2) we've already cloned a copy 347 # from an earlier (more derived) base 348 if cls._init_dict.has_key(key) or cls._values.has_key(key): 349 continue 350 351 if isConfigNode(val): 352 cls._values[key] = val() 353 elif isSimObjSequence(val) and len(val): 354 cls._values[key] = [ v() for v in val ] 355 356 cls._params.parent = cnbase[0]._params 357 cls._values.parent = cnbase[0]._values 358 359 elif len(cnbase) > 1: 360 panic("""\ 361The config hierarchy only supports single inheritence of SimObject 362classes. You're trying to derive from: 363%s""" % str(cnbase)) 364 365 # process param types from _init_dict, as these may be needed 366 # by param descriptions also in _init_dict 367 for key,val in cls._init_dict.items(): 368 if isinstance(val, type) and issubclass(val, ParamType): 369 cls._param_types[key] = val 370 if not issubclass(val, ConfigNode): 371 del cls._init_dict[key] 372 373 # now process remaining _init_dict items 374 for key,val in cls._init_dict.items(): 375 # param descriptions 376 if isinstance(val, ParamBase): 377 cls._new_param(key, val) 378 379 # init-time-only keywords 380 elif cls.init_keywords.has_key(key): 381 cls._set_keyword(key, val, cls.init_keywords[key]) 382 383 # See description of decorators in the importer.py file. 384 # We just strip off the expr_decorator now since we don't 385 # need from this point on. 386 elif key.startswith(expr_decorator): 387 key = key[len(expr_decorator):] 388 # because it had dots into a list so that we can find the 389 # proper variable to modify. 390 key = key.split(dot_decorator) 391 c = cls 392 for item in key[:-1]: 393 c = getattr(c, item) 394 setattr(c, key[-1], val) 395 396 # default: use normal path (ends up in __setattr__) 397 else: 398 setattr(cls, key, val) 399 400 def _set_keyword(cls, keyword, val, kwtype): 401 if not isinstance(val, kwtype): 402 raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ 403 (keyword, type(val), kwtype) 404 if isinstance(val, types.FunctionType): 405 val = classmethod(val) 406 type.__setattr__(cls, keyword, val) 407 408 def _new_param(cls, name, value): 409 cls._params[name] = value 410 if hasattr(value, 'default'): 411 cls._values[name] = value.default 412 # try to resolve local param types in local param_types scope 413 value.maybe_resolve_type(cls._param_types) 414 415 # Set attribute (called on foo.attr = value when foo is an 416 # instance of class cls). 417 def __setattr__(cls, attr, value): 418 # normal processing for private attributes 419 if attr.startswith('_'): 420 type.__setattr__(cls, attr, value) 421 return 422 423 if cls.keywords.has_key(attr): 424 cls._set_keyword(attr, value, cls.keywords[attr]) 425 return 426 427 # must be SimObject param 428 param = cls._params.get(attr, None) 429 if param: 430 # It's ok: set attribute by delegating to 'object' class. 431 # Note the use of param.make_value() to verify/canonicalize 432 # the assigned value 433 try: 434 param.valid(value) 435 except Exception, e: 436 raise e.__class__, "%s\nError setting param %s.%s to %s\n" % \ 437 (e, cls.__name__, attr, value) 438 cls._values[attr] = value 439 elif isConfigNode(value) or isSimObjSequence(value): 440 cls._values[attr] = value 441 else: 442 raise AttributeError, \ 443 "Class %s has no parameter %s" % (cls.__name__, attr) 444 445 def __getattr__(cls, attr): 446 if cls._params.has_key(attr) or cls._values.has_key(attr): 447 return Value(cls, attr) 448 449 if attr == '_cpp_param_decl' and hasattr(cls, 'type'): 450 return cls.type + '*' 451 452 raise AttributeError, \ 453 "object '%s' has no attribute '%s'" % (cls.__name__, attr) 454 455 def add_child(cls, instance, name, child): 456 if isNullPointer(child) or instance.top_child_names.has_key(name): 457 return 458 459 if isinstance(child, (list, tuple)): 460 kid = [] 461 for i,c in enumerate(child): 462 n = '%s%d' % (name, i) 463 k = c.instantiate(n, instance) 464 465 instance.children.append(k) 466 instance.child_names[n] = k 467 instance.child_objects[c] = k 468 kid.append(k) 469 else: 470 kid = child.instantiate(name, instance) 471 instance.children.append(kid) 472 instance.child_names[name] = kid 473 instance.child_objects[child] = kid 474 475 instance.top_child_names[name] = kid 476 477 # Print instance info to .ini file. 478 def instantiate(cls, name, parent = None): 479 instance = Node(name, cls, parent, isParamContext(cls)) 480 481 if hasattr(cls, 'check'): 482 cls.check() 483 484 for key,value in cls._values.iteritems(): 485 if isConfigNode(value): 486 cls.add_child(instance, key, value) 487 if isinstance(value, (list, tuple)): 488 vals = [ v for v in value if isConfigNode(v) ] 489 if len(vals): 490 cls.add_child(instance, key, vals) 491 492 for pname,param in cls._params.iteritems(): 493 value = cls._values.get(pname, None) 494 if value is None: 495 panic('Error getting %s from %s' % (pname, name)) 496 497 try: 498 if isConfigNode(value): 499 value = instance.child_objects[value] 500 elif isinstance(value, (list, tuple)): 501 v = [] 502 for val in value: 503 if isConfigNode(val): 504 v.append(instance.child_objects[val]) 505 else: 506 v.append(val) 507 value = v 508 509 p = NodeParam(pname, param, value) 510 instance.params.append(p) 511 instance.param_names[pname] = p 512 except Exception, e: 513 raise e.__class__, 'Exception while evaluating %s.%s\n%s' % \ 514 (instance.path, pname, e) 515 516 return instance 517 518 def _convert(cls, value): 519 realvalue = value 520 if isinstance(value, Node): 521 realvalue = value.realtype 522 523 if isinstance(realvalue, Proxy): 524 return value 525 526 if realvalue == None or isNullPointer(realvalue): 527 return value 528 529 if isSubClass(realvalue, cls): 530 return value 531 532 raise TypeError, 'object %s type %s wrong type, should be %s' % \ 533 (repr(realvalue), realvalue, cls) 534 535 def _string(cls, value): 536 if isNullPointer(value): 537 return 'Null' 538 return Node._string(value) 539 540# The ConfigNode class is the root of the special hierarchy. Most of 541# the code in this class deals with the configuration hierarchy itself 542# (parent/child node relationships). 543class ConfigNode(object): 544 # Specify metaclass. Any class inheriting from ConfigNode will 545 # get this metaclass. 546 __metaclass__ = MetaConfigNode 547 548 def __new__(cls, **kwargs): 549 name = cls.__name__ + ("_%d" % cls._anon_subclass_counter) 550 cls._anon_subclass_counter += 1 551 return cls.__metaclass__(name, (cls, ), kwargs) 552 553class ParamContext(ConfigNode,ParamType): 554 pass 555 556class MetaSimObject(MetaConfigNode): 557 # init_keywords and keywords are inherited from MetaConfigNode, 558 # with overrides/additions 559 init_keywords = MetaConfigNode.init_keywords 560 init_keywords.update({ 'abstract' : types.BooleanType, 561 'type' : types.StringType }) 562 563 keywords = MetaConfigNode.keywords 564 # no additional keywords 565 566 cpp_classes = [] 567 568 # initialization 569 def __init__(cls, name, bases, dict): 570 super(MetaSimObject, cls).__init__(name, bases, dict) 571 572 if hasattr(cls, 'type'): 573 if name == 'SimObject': 574 cls._cpp_base = None 575 elif hasattr(cls._bases[1], 'type'): 576 cls._cpp_base = cls._bases[1].type 577 else: 578 panic("SimObject %s derives from a non-C++ SimObject %s "\ 579 "(no 'type')" % (cls, cls_bases[1].__name__)) 580 581 # This class corresponds to a C++ class: put it on the global 582 # list of C++ objects to generate param structs, etc. 583 MetaSimObject.cpp_classes.append(cls) 584 585 def _cpp_decl(cls): 586 name = cls.__name__ 587 code = "" 588 code += "\n".join([e.cpp_declare() for e in cls._param_types.values()]) 589 code += "\n" 590 param_names = cls._params.keys() 591 param_names.sort() 592 code += "struct Params" 593 if cls._cpp_base: 594 code += " : public %s::Params" % cls._cpp_base 595 code += " {\n " 596 code += "\n ".join([cls._params[pname].cpp_decl(pname) \ 597 for pname in param_names]) 598 code += "\n};\n" 599 return code 600 601class NodeParam(object): 602 def __init__(self, name, param, value): 603 self.name = name 604 self.param = param 605 self.ptype = param.ptype 606 self.convert = param.convert 607 self.string = param.string 608 self.value = value 609 610class Node(object): 611 all = {} 612 def __init__(self, name, realtype, parent, paramcontext): 613 self.name = name 614 self.realtype = realtype 615 if isSimObject(realtype): 616 self.type = realtype.type 617 else: 618 self.type = None 619 self.parent = parent 620 self.children = [] 621 self.child_names = {} 622 self.child_objects = {} 623 self.top_child_names = {} 624 self.params = [] 625 self.param_names = {} 626 self.paramcontext = paramcontext 627 628 path = [ self.name ] 629 node = self.parent 630 while node is not None: 631 if node.name != 'root': 632 path.insert(0, node.name) 633 else: 634 assert(node.parent is None) 635 node = node.parent 636 self.path = '.'.join(path) 637 638 def find(self, realtype, path): 639 if not path: 640 if issubclass(self.realtype, realtype): 641 return self, True 642 643 obj = None 644 for child in self.children: 645 if issubclass(child.realtype, realtype): 646 if obj is not None: 647 raise AttributeError, \ 648 'parent.any matched more than one: %s %s' % \ 649 (obj.path, child.path) 650 obj = child 651 return obj, obj is not None 652 653 try: 654 obj = self 655 for (node,index) in path[:-1]: 656 if obj.child_names.has_key(node): 657 obj = obj.child_names[node] 658 else: 659 obj = obj.top_child_names[node] 660 obj = Proxy.getindex(obj, index) 661 662 (last,index) = path[-1] 663 if obj.child_names.has_key(last): 664 value = obj.child_names[last] 665 return Proxy.getindex(value, index), True 666 elif obj.top_child_names.has_key(last): 667 value = obj.top_child_names[last] 668 return Proxy.getindex(value, index), True 669 elif obj.param_names.has_key(last): 670 value = obj.param_names[last] 671 realtype._convert(value.value) 672 return Proxy.getindex(value.value, index), True 673 except KeyError: 674 pass 675 676 return None, False 677 678 def unproxy(self, param, ptype): 679 if not isinstance(param, Proxy): 680 return param 681 return param.unproxy(self, ptype) 682 683 def fixup(self): 684 self.all[self.path] = self 685 686 for param in self.params: 687 ptype = param.ptype 688 pval = param.value 689 690 try: 691 if isinstance(pval, (list, tuple)): 692 param.value = [ self.unproxy(pv, ptype) for pv in pval ] 693 else: 694 param.value = self.unproxy(pval, ptype) 695 except Exception, e: 696 raise e.__class__, 'Error while fixing up %s:%s\n%s' % \ 697 (self.path, param.name, e) 698 699 for child in self.children: 700 assert(child != self) 701 child.fixup() 702 703 # print type and parameter values to .ini file 704 def display(self): 705 print '[' + self.path + ']' # .ini section header 706 707 if isSimObject(self.realtype): 708 print 'type = %s' % self.type 709 710 if self.children: 711 # instantiate children in same order they were added for 712 # backward compatibility (else we can end up with cpu1 713 # before cpu0). Changing ordering can also influence timing 714 # in the current memory system, as caches get added to a bus 715 # in different orders which affects their priority in the 716 # case of simulataneous requests. 717 self.children.sort(lambda x,y: cmp(x.name, y.name)) 718 children = [ c.name for c in self.children if not c.paramcontext] 719 print 'children =', ' '.join(children) 720 721 self.params.sort(lambda x,y: cmp(x.name, y.name)) 722 for param in self.params: 723 try: 724 if param.value is None: 725 raise AttributeError, 'Parameter with no value' 726 727 value = param.convert(param.value) 728 string = param.string(value) 729 except Exception, e: 730 raise e.__class__, 'exception in %s:%s\n%s' % \ 731 (self.path, param.name, e) 732 733 print '%s = %s' % (param.name, string) 734 735 print 736 737 # recursively dump out children 738 for c in self.children: 739 c.display() 740 741 # print type and parameter values to .ini file 742 def outputDot(self, dot): 743 744 745 label = "{%s|" % self.path 746 if isSimObject(self.realtype): 747 label += '%s|' % self.type 748 749 if self.children: 750 # instantiate children in same order they were added for 751 # backward compatibility (else we can end up with cpu1 752 # before cpu0). 753 for c in self.children: 754 dot.add_edge(pydot.Edge(self.path,c.path, style="bold")) 755 756 simobjs = [] 757 for param in self.params: 758 try: 759 if param.value is None: 760 raise AttributeError, 'Parameter with no value' 761 762 value = param.convert(param.value) 763 string = param.string(value) 764 except Exception, e: 765 raise e.__class__, 'exception in %s:%s\n%s' % \ 766 (self.name, param.name, e) 767 raise 768 if isConfigNode(param.ptype) and string != "Null": 769 simobjs.append(string) 770 else: 771 label += '%s = %s\\n' % (param.name, string) 772 773 for so in simobjs: 774 label += "|<%s> %s" % (so, so) 775 dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, 776 tailport="w")) 777 label += '}' 778 dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) 779 780 # recursively dump out children 781 for c in self.children: 782 c.outputDot(dot) 783 784 def _string(cls, value): 785 if not isinstance(value, Node): 786 raise AttributeError, 'expecting %s got %s' % (Node, value) 787 return value.path 788 _string = classmethod(_string) 789 790##################################################################### 791# 792# Parameter description classes 793# 794# The _params dictionary in each class maps parameter names to 795# either a Param or a VectorParam object. These objects contain the 796# parameter description string, the parameter type, and the default 797# value (loaded from the PARAM section of the .odesc files). The 798# _convert() method on these objects is used to force whatever value 799# is assigned to the parameter to the appropriate type. 800# 801# Note that the default values are loaded into the class's attribute 802# space when the parameter dictionary is initialized (in 803# MetaConfigNode._setparams()); after that point they aren't used. 804# 805##################################################################### 806 807def isNullPointer(value): 808 return isinstance(value, NullSimObject) 809 810class Value(object): 811 def __init__(self, obj, attr): 812 super(Value, self).__setattr__('attr', attr) 813 super(Value, self).__setattr__('obj', obj) 814 815 def _getattr(self): 816 return self.obj._values.get(self.attr) 817 818 def __setattr__(self, attr, value): 819 setattr(self._getattr(), attr, value) 820 821 def __getattr__(self, attr): 822 return getattr(self._getattr(), attr) 823 824 def __getitem__(self, index): 825 return self._getattr().__getitem__(index) 826 827 def __call__(self, *args, **kwargs): 828 return self._getattr().__call__(*args, **kwargs) 829 830 def __nonzero__(self): 831 return bool(self._getattr()) 832 833 def __str__(self): 834 return str(self._getattr()) 835 836 def __len__(self): 837 return len(self._getattr()) 838 839# Regular parameter. 840class ParamBase(object): 841 def __init__(self, ptype, *args, **kwargs): 842 if isinstance(ptype, types.StringType): 843 self.ptype_string = ptype 844 elif isinstance(ptype, type): 845 self.ptype = ptype 846 else: 847 raise TypeError, "Param type is not a type (%s)" % ptype 848 849 if args: 850 if len(args) == 1: 851 self.desc = args[0] 852 elif len(args) == 2: 853 self.default = args[0] 854 self.desc = args[1] 855 else: 856 raise TypeError, 'too many arguments' 857 858 if kwargs.has_key('desc'): 859 assert(not hasattr(self, 'desc')) 860 self.desc = kwargs['desc'] 861 del kwargs['desc'] 862 863 if kwargs.has_key('default'): 864 assert(not hasattr(self, 'default')) 865 self.default = kwargs['default'] 866 del kwargs['default'] 867 868 if kwargs: 869 raise TypeError, 'extra unknown kwargs %s' % kwargs 870 871 if not hasattr(self, 'desc'): 872 raise TypeError, 'desc attribute missing' 873 874 def maybe_resolve_type(self, context): 875 # check if already resolved... don't use hasattr(), 876 # as that calls __getattr__() 877 if self.__dict__.has_key('ptype'): 878 return 879 try: 880 self.ptype = context[self.ptype_string] 881 except KeyError: 882 # no harm in trying... we'll try again later using global scope 883 pass 884 885 def __getattr__(self, attr): 886 if attr == 'ptype': 887 try: 888 self.ptype = param_types[self.ptype_string] 889 return self.ptype 890 except: 891 panic("undefined Param type %s" % self.ptype_string) 892 else: 893 raise AttributeError, "'%s' object has no attribute '%s'" % \ 894 (type(self).__name__, attr) 895 896 def valid(self, value): 897 if not isinstance(value, Proxy): 898 self.ptype._convert(value) 899 900 def convert(self, value): 901 return self.ptype._convert(value) 902 903 def string(self, value): 904 return self.ptype._string(value) 905 906 def set(self, name, instance, value): 907 instance.__dict__[name] = value 908 909 def cpp_decl(self, name): 910 return '%s %s;' % (self.ptype._cpp_param_decl, name) 911 912class ParamFactory(object): 913 def __init__(self, type): 914 self.ptype = type 915 916 # E.g., Param.Int(5, "number of widgets") 917 def __call__(self, *args, **kwargs): 918 return ParamBase(self.ptype, *args, **kwargs) 919 920 # Strange magic to theoretically allow dotted names as Param classes, 921 # e.g., Param.Foo.Bar(...) to have a param of type Foo.Bar 922 def __getattr__(self, attr): 923 if attr == '__bases__': 924 raise AttributeError, '' 925 cls = type(self) 926 return cls(attr) 927 928 def __setattr__(self, attr, value): 929 if attr != 'ptype': 930 raise AttributeError, \ 931 'Attribute %s not available in %s' % (attr, self.__class__) 932 super(ParamFactory, self).__setattr__(attr, value) 933 934 935Param = ParamFactory(None) 936 937# Vector-valued parameter description. Just like Param, except that 938# the value is a vector (list) of the specified type instead of a 939# single value. 940class VectorParamBase(ParamBase): 941 def __init__(self, type, *args, **kwargs): 942 ParamBase.__init__(self, type, *args, **kwargs) 943 944 def valid(self, value): 945 if value == None: 946 return True 947 948 if isinstance(value, (list, tuple)): 949 for val in value: 950 if not isinstance(val, Proxy): 951 self.ptype._convert(val) 952 elif not isinstance(value, Proxy): 953 self.ptype._convert(value) 954 955 # Convert assigned value to appropriate type. If the RHS is not a 956 # list or tuple, it generates a single-element list. 957 def convert(self, value): 958 if value == None: 959 return [] 960 961 if isinstance(value, (list, tuple)): 962 # list: coerce each element into new list 963 return [ self.ptype._convert(v) for v in value ] 964 else: 965 # singleton: coerce & wrap in a list 966 return self.ptype._convert(value) 967 968 def string(self, value): 969 if isinstance(value, (list, tuple)): 970 return ' '.join([ self.ptype._string(v) for v in value]) 971 else: 972 return self.ptype._string(value) 973 974 def cpp_decl(self, name): 975 return 'std::vector<%s> %s;' % (self.ptype._cpp_param_decl, name) 976 977class VectorParamFactory(ParamFactory): 978 # E.g., VectorParam.Int(5, "number of widgets") 979 def __call__(self, *args, **kwargs): 980 return VectorParamBase(self.ptype, *args, **kwargs) 981 982VectorParam = VectorParamFactory(None) 983 984##################################################################### 985# 986# Parameter Types 987# 988# Though native Python types could be used to specify parameter types 989# (the 'ptype' field of the Param and VectorParam classes), it's more 990# flexible to define our own set of types. This gives us more control 991# over how Python expressions are converted to values (via the 992# __init__() constructor) and how these values are printed out (via 993# the __str__() conversion method). Eventually we'll need these types 994# to correspond to distinct C++ types as well. 995# 996##################################################################### 997 998class MetaRange(type): 999 def __init__(cls, name, bases, dict): 1000 super(MetaRange, cls).__init__(name, bases, dict) 1001 if name == 'Range': 1002 return 1003 cls._cpp_param_decl = 'Range<%s>' % cls.type._cpp_param_decl 1004 1005 def _convert(cls, value): 1006 if not isinstance(value, Range): 1007 raise TypeError, 'value %s is not a Pair' % value 1008 value = cls(value) 1009 value.first = cls.type._convert(value.first) 1010 value.second = cls.type._convert(value.second) 1011 return value 1012 1013 def _string(cls, value): 1014 first = int(value.first) 1015 second = int(value.second) 1016 if value.extend: 1017 second += first 1018 if not value.inclusive: 1019 second -= 1 1020 return '%s:%s' % (cls.type._string(first), cls.type._string(second)) 1021 1022class Range(ParamType): 1023 __metaclass__ = MetaRange 1024 def __init__(self, *args, **kwargs): 1025 if len(args) == 0: 1026 self.first = kwargs.pop('start') 1027 1028 if 'end' in kwargs: 1029 self.second = kwargs.pop('end') 1030 self.inclusive = True 1031 self.extend = False 1032 elif 'size' in kwargs: 1033 self.second = kwargs.pop('size') 1034 self.inclusive = False 1035 self.extend = True 1036 else: 1037 raise TypeError, "Either end or size must be specified" 1038 1039 elif len(args) == 1: 1040 if kwargs: 1041 self.first = args[0] 1042 if 'end' in kwargs: 1043 self.second = kwargs.pop('end') 1044 self.inclusive = True 1045 self.extend = False 1046 elif 'size' in kwargs: 1047 self.second = kwargs.pop('size') 1048 self.inclusive = False 1049 self.extend = True 1050 else: 1051 raise TypeError, "Either end or size must be specified" 1052 elif isinstance(args[0], Range): 1053 self.first = args[0].first 1054 self.second = args[0].second 1055 self.inclusive = args[0].inclusive 1056 self.extend = args[0].extend 1057 else: 1058 self.first = 0 1059 self.second = args[0] 1060 self.inclusive = False 1061 self.extend = True 1062 1063 elif len(args) == 2: 1064 self.first, self.second = args 1065 self.inclusive = True 1066 self.extend = False 1067 else: 1068 raise TypeError, "Too many arguments specified" 1069 1070 if kwargs: 1071 raise TypeError, "too many keywords: %s" % kwargs.keys() 1072 1073# Metaclass for bounds-checked integer parameters. See CheckedInt. 1074class CheckedIntType(type): 1075 def __init__(cls, name, bases, dict): 1076 super(CheckedIntType, cls).__init__(name, bases, dict) 1077 1078 # CheckedInt is an abstract base class, so we actually don't 1079 # want to do any processing on it... the rest of this code is 1080 # just for classes that derive from CheckedInt. 1081 if name == 'CheckedInt': 1082 return 1083 1084 if not (hasattr(cls, 'min') and hasattr(cls, 'max')): 1085 if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): 1086 panic("CheckedInt subclass %s must define either\n" \ 1087 " 'min' and 'max' or 'size' and 'unsigned'\n" \ 1088 % name); 1089 if cls.unsigned: 1090 cls.min = 0 1091 cls.max = 2 ** cls.size - 1 1092 else: 1093 cls.min = -(2 ** (cls.size - 1)) 1094 cls.max = (2 ** (cls.size - 1)) - 1 1095 1096 cls._cpp_param_decl = cls.cppname 1097 1098 def _convert(cls, value): 1099 if isinstance(value, bool): 1100 return int(value) 1101 1102 if not isinstance(value, (int, long, float, str)): 1103 raise TypeError, 'Integer param of invalid type %s' % type(value) 1104 1105 if isinstance(value, float): 1106 value = long(value) 1107 elif isinstance(value, str): 1108 value = toInteger(value) 1109 1110 if not cls.min <= value <= cls.max: 1111 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ 1112 (cls.min, value, cls.max) 1113 1114 return value 1115 1116 def _string(cls, value): 1117 return str(value) 1118 1119# Abstract superclass for bounds-checked integer parameters. This 1120# class is subclassed to generate parameter classes with specific 1121# bounds. Initialization of the min and max bounds is done in the 1122# metaclass CheckedIntType.__init__. 1123class CheckedInt(long,ParamType): 1124 __metaclass__ = CheckedIntType 1125 1126class Int(CheckedInt): cppname = 'int'; size = 32; unsigned = False 1127class Unsigned(CheckedInt): cppname = 'unsigned'; size = 32; unsigned = True 1128 1129class Int8(CheckedInt): cppname = 'int8_t'; size = 8; unsigned = False 1130class UInt8(CheckedInt): cppname = 'uint8_t'; size = 8; unsigned = True 1131class Int16(CheckedInt): cppname = 'int16_t'; size = 16; unsigned = False 1132class UInt16(CheckedInt): cppname = 'uint16_t'; size = 16; unsigned = True 1133class Int32(CheckedInt): cppname = 'int32_t'; size = 32; unsigned = False 1134class UInt32(CheckedInt): cppname = 'uint32_t'; size = 32; unsigned = True 1135class Int64(CheckedInt): cppname = 'int64_t'; size = 64; unsigned = False 1136class UInt64(CheckedInt): cppname = 'uint64_t'; size = 64; unsigned = True 1137 1138class Counter(CheckedInt): cppname = 'Counter'; size = 64; unsigned = True 1139class Tick(CheckedInt): cppname = 'Tick'; size = 64; unsigned = True 1140 1141class Percent(CheckedInt): cppname = 'int'; min = 0; max = 100 1142 1143class MemorySize(CheckedInt): 1144 cppname = 'uint64_t' 1145 size = 64 1146 unsigned = True 1147 def __new__(cls, value): 1148 return super(MemorySize, cls).__new__(cls, toMemorySize(value)) 1149 1150 def _convert(cls, value): 1151 return cls(value) 1152 _convert = classmethod(_convert) 1153 1154 def _string(cls, value): 1155 return '%d' % value 1156 _string = classmethod(_string) 1157 1158class Addr(MemorySize): 1159 pass 1160 1161class AddrRange(Range): 1162 type = Addr 1163 1164# Boolean parameter type. 1165class Bool(ParamType): 1166 _cpp_param_decl = 'bool' 1167 #def __new__(cls, value): 1168 # return super(MemorySize, cls).__new__(cls, toBool(value)) 1169 1170 def _convert(cls, value): 1171 return toBool(value) 1172 _convert = classmethod(_convert) 1173 1174 def _string(cls, value): 1175 if value: 1176 return "true" 1177 else: 1178 return "false" 1179 _string = classmethod(_string) 1180 1181# String-valued parameter. 1182class String(ParamType): 1183 _cpp_param_decl = 'string' 1184 1185 # Constructor. Value must be Python string. 1186 def _convert(cls,value): 1187 if value is None: 1188 return '' 1189 if isinstance(value, str): 1190 return value 1191 1192 raise TypeError, \ 1193 "String param got value %s %s" % (repr(value), type(value)) 1194 _convert = classmethod(_convert) 1195 1196 # Generate printable string version. Not too tricky. 1197 def _string(cls, value): 1198 return value 1199 _string = classmethod(_string) 1200 1201 1202def IncEthernetAddr(addr, val = 1): 1203 bytes = map(lambda x: int(x, 16), addr.split(':')) 1204 bytes[5] += val 1205 for i in (5, 4, 3, 2, 1): 1206 val,rem = divmod(bytes[i], 256) 1207 bytes[i] = rem 1208 if val == 0: 1209 break 1210 bytes[i - 1] += val 1211 assert(bytes[0] <= 255) 1212 return ':'.join(map(lambda x: '%02x' % x, bytes)) 1213 1214class NextEthernetAddr(object): 1215 __metaclass__ = Singleton 1216 addr = "00:90:00:00:00:01" 1217 1218 def __init__(self, inc = 1): 1219 self.value = self.addr 1220 self.addr = IncEthernetAddr(self.addr, inc) 1221 1222class EthernetAddr(ParamType): 1223 _cpp_param_decl = 'EthAddr' 1224 1225 def _convert(cls, value): 1226 if value == NextEthernetAddr: 1227 return value 1228 1229 if not isinstance(value, str): 1230 raise TypeError, "expected an ethernet address and didn't get one" 1231 1232 bytes = value.split(':') 1233 if len(bytes) != 6: 1234 raise TypeError, 'invalid ethernet address %s' % value 1235 1236 for byte in bytes: 1237 if not 0 <= int(byte) <= 256: 1238 raise TypeError, 'invalid ethernet address %s' % value 1239 1240 return value 1241 _convert = classmethod(_convert) 1242 1243 def _string(cls, value): 1244 if value == NextEthernetAddr: 1245 value = value().value 1246 return value 1247 _string = classmethod(_string) 1248 1249# Special class for NULL pointers. Note the special check in 1250# make_param_value() above that lets these be assigned where a 1251# SimObject is required. 1252# only one copy of a particular node 1253class NullSimObject(object): 1254 __metaclass__ = Singleton 1255 1256 def __call__(cls): 1257 return cls 1258 1259 def _instantiate(self, parent = None, path = ''): 1260 pass 1261 1262 def _convert(cls, value): 1263 if value == Nxone: 1264 return 1265 1266 if isinstance(value, cls): 1267 return value 1268 1269 raise TypeError, 'object %s %s of the wrong type, should be %s' % \ 1270 (repr(value), type(value), cls) 1271 _convert = classmethod(_convert) 1272 1273 def _string(): 1274 return 'NULL' 1275 _string = staticmethod(_string) 1276 1277# The only instance you'll ever need... 1278Null = NULL = NullSimObject() 1279 1280# Enumerated types are a little more complex. The user specifies the 1281# type as Enum(foo) where foo is either a list or dictionary of 1282# alternatives (typically strings, but not necessarily so). (In the 1283# long run, the integer value of the parameter will be the list index 1284# or the corresponding dictionary value. For now, since we only check 1285# that the alternative is valid and then spit it into a .ini file, 1286# there's not much point in using the dictionary.) 1287 1288# What Enum() must do is generate a new type encapsulating the 1289# provided list/dictionary so that specific values of the parameter 1290# can be instances of that type. We define two hidden internal 1291# classes (_ListEnum and _DictEnum) to serve as base classes, then 1292# derive the new type from the appropriate base class on the fly. 1293 1294 1295# Metaclass for Enum types 1296class MetaEnum(type): 1297 1298 def __init__(cls, name, bases, init_dict): 1299 if init_dict.has_key('map'): 1300 if not isinstance(cls.map, dict): 1301 raise TypeError, "Enum-derived class attribute 'map' " \ 1302 "must be of type dict" 1303 # build list of value strings from map 1304 cls.vals = cls.map.keys() 1305 cls.vals.sort() 1306 elif init_dict.has_key('vals'): 1307 if not isinstance(cls.vals, list): 1308 raise TypeError, "Enum-derived class attribute 'vals' " \ 1309 "must be of type list" 1310 # build string->value map from vals sequence 1311 cls.map = {} 1312 for idx,val in enumerate(cls.vals): 1313 cls.map[val] = idx 1314 else: 1315 raise TypeError, "Enum-derived class must define "\ 1316 "attribute 'map' or 'vals'" 1317 1318 cls._cpp_param_decl = name 1319 1320 super(MetaEnum, cls).__init__(name, bases, init_dict) 1321 1322 def cpp_declare(cls): 1323 s = 'enum %s {\n ' % cls.__name__ 1324 s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) 1325 s += '\n};\n' 1326 return s 1327 1328# Base class for enum types. 1329class Enum(ParamType): 1330 __metaclass__ = MetaEnum 1331 vals = [] 1332 1333 def _convert(self, value): 1334 if value not in self.map: 1335 raise TypeError, "Enum param got bad value '%s' (not in %s)" \ 1336 % (value, self.vals) 1337 return value 1338 _convert = classmethod(_convert) 1339 1340 # Generate printable string version of value. 1341 def _string(self, value): 1342 return str(value) 1343 _string = classmethod(_string) 1344# 1345# "Constants"... handy aliases for various values. 1346# 1347 1348# Some memory range specifications use this as a default upper bound. 1349MaxAddr = Addr.max 1350MaxTick = Tick.max 1351AllMemory = AddrRange(0, MaxAddr) 1352 1353##################################################################### 1354 1355# The final hook to generate .ini files. Called from configuration 1356# script once config is built. 1357def instantiate(root): 1358 instance = root.instantiate('root') 1359 instance.fixup() 1360 instance.display() 1361 if not noDot: 1362 dot = pydot.Dot() 1363 instance.outputDot(dot) 1364 dot.orientation = "portrait" 1365 dot.size = "8.5,11" 1366 dot.ranksep="equally" 1367 dot.rank="samerank" 1368 dot.write("config.dot") 1369 dot.write_ps("config.ps") 1370 1371# SimObject is a minimal extension of ConfigNode, implementing a 1372# hierarchy node that corresponds to an M5 SimObject. It prints out a 1373# "type=" line to indicate its SimObject class, prints out the 1374# assigned parameters corresponding to its class, and allows 1375# parameters to be set by keyword in the constructor. Note that most 1376# of the heavy lifting for the SimObject param handling is done in the 1377# MetaConfigNode metaclass. 1378class SimObject(ConfigNode, ParamType): 1379 __metaclass__ = MetaSimObject 1380 type = 'SimObject' 1381 1382 1383# __all__ defines the list of symbols that get exported when 1384# 'from config import *' is invoked. Try to keep this reasonably 1385# short to avoid polluting other namespaces. 1386__all__ = ['ConfigNode', 'SimObject', 'ParamContext', 'Param', 'VectorParam', 1387 'parent', 'Enum', 1388 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', 1389 'Int32', 'UInt32', 'Int64', 'UInt64', 1390 'Counter', 'Addr', 'Tick', 'Percent', 1391 'MemorySize', 1392 'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory', 'NULL', 1393 'NextEthernetAddr', 'instantiate'] 1394