SimObject.py revision 1814
1# Copyright (c) 2004-2005 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 30import m5 31panic = m5.panic 32from convert import * 33from multidict import multidict 34 35noDot = False 36try: 37 import pydot 38except: 39 noDot = True 40 41class Singleton(type): 42 def __call__(cls, *args, **kwargs): 43 if hasattr(cls, '_instance'): 44 return cls._instance 45 46 cls._instance = super(Singleton, cls).__call__(*args, **kwargs) 47 return cls._instance 48 49##################################################################### 50# 51# M5 Python Configuration Utility 52# 53# The basic idea is to write simple Python programs that build Python 54# objects corresponding to M5 SimObjects for the desired simulation 55# configuration. For now, the Python emits a .ini file that can be 56# parsed by M5. In the future, some tighter integration between M5 57# and the Python interpreter may allow bypassing the .ini file. 58# 59# Each SimObject class in M5 is represented by a Python class with the 60# same name. The Python inheritance tree mirrors the M5 C++ tree 61# (e.g., SimpleCPU derives from BaseCPU in both cases, and all 62# SimObjects inherit from a single SimObject base class). To specify 63# an instance of an M5 SimObject in a configuration, the user simply 64# instantiates the corresponding Python object. The parameters for 65# that SimObject are given by assigning to attributes of the Python 66# object, either using keyword assignment in the constructor or in 67# separate assignment statements. For example: 68# 69# cache = BaseCache(size='64KB') 70# cache.hit_latency = 3 71# cache.assoc = 8 72# 73# The magic lies in the mapping of the Python attributes for SimObject 74# classes to the actual SimObject parameter specifications. This 75# allows parameter validity checking in the Python code. Continuing 76# the example above, the statements "cache.blurfl=3" or 77# "cache.assoc='hello'" would both result in runtime errors in Python, 78# since the BaseCache object has no 'blurfl' parameter and the 'assoc' 79# parameter requires an integer, respectively. This magic is done 80# primarily by overriding the special __setattr__ method that controls 81# assignment to object attributes. 82# 83# Once a set of Python objects have been instantiated in a hierarchy, 84# calling 'instantiate(obj)' (where obj is the root of the hierarchy) 85# will generate a .ini file. See simple-4cpu.py for an example 86# (corresponding to m5-test/simple-4cpu.ini). 87# 88##################################################################### 89 90##################################################################### 91# 92# ConfigNode/SimObject classes 93# 94# The Python class hierarchy rooted by ConfigNode (which is the base 95# class of SimObject, which in turn is the base class of all other M5 96# SimObject classes) has special attribute behavior. In general, an 97# object in this hierarchy has three categories of attribute-like 98# things: 99# 100# 1. Regular Python methods and variables. These must start with an 101# underscore to be treated normally. 102# 103# 2. SimObject parameters. These values are stored as normal Python 104# attributes, but all assignments to these attributes are checked 105# against the pre-defined set of parameters stored in the class's 106# _params dictionary. Assignments to attributes that do not 107# correspond to predefined parameters, or that are not of the correct 108# type, incur runtime errors. 109# 110# 3. Hierarchy children. The child nodes of a ConfigNode are stored 111# in the node's _children dictionary, but can be accessed using the 112# Python attribute dot-notation (just as they are printed out by the 113# simulator). Children cannot be created using attribute assigment; 114# they must be added by specifying the parent node in the child's 115# constructor or using the '+=' operator. 116 117# The SimObject parameters are the most complex, for a few reasons. 118# First, both parameter descriptions and parameter values are 119# inherited. Thus parameter description lookup must go up the 120# inheritance chain like normal attribute lookup, but this behavior 121# must be explicitly coded since the lookup occurs in each class's 122# _params attribute. Second, because parameter values can be set 123# on SimObject classes (to implement default values), the parameter 124# checking behavior must be enforced on class attribute assignments as 125# well as instance attribute assignments. Finally, because we allow 126# class specialization via inheritance (e.g., see the L1Cache class in 127# the simple-4cpu.py example), we must do parameter checking even on 128# class instantiation. To provide all these features, we use a 129# metaclass to define most of the SimObject parameter behavior for 130# this class hierarchy. 131# 132##################################################################### 133 134def isSimObject(value): 135 return isinstance(value, SimObject) 136 137def isSimObjSequence(value): 138 if not isinstance(value, (list, tuple)): 139 return False 140 141 for val in value: 142 if not isNullPointer(val) and not isSimObject(val): 143 return False 144 145 return True 146 147def isNullPointer(value): 148 return isinstance(value, NullSimObject) 149 150# The metaclass for ConfigNode (and thus for everything that derives 151# from ConfigNode, including SimObject). This class controls how new 152# classes that derive from ConfigNode are instantiated, and provides 153# inherited class behavior (just like a class controls how instances 154# of that class are instantiated, and provides inherited instance 155# behavior). 156class MetaSimObject(type): 157 # Attributes that can be set only at initialization time 158 init_keywords = { 'abstract' : types.BooleanType, 159 'type' : types.StringType } 160 # Attributes that can be set any time 161 keywords = { 'check' : types.FunctionType, 162 'children' : types.ListType } 163 164 # __new__ is called before __init__, and is where the statements 165 # in the body of the class definition get loaded into the class's 166 # __dict__. We intercept this to filter out parameter assignments 167 # and only allow "private" attributes to be passed to the base 168 # __new__ (starting with underscore). 169 def __new__(mcls, name, bases, dict): 170 # Copy "private" attributes (including special methods such as __new__) 171 # to the official dict. Everything else goes in _init_dict to be 172 # filtered in __init__. 173 cls_dict = {} 174 for key,val in dict.items(): 175 if key.startswith('_'): 176 cls_dict[key] = val 177 del dict[key] 178 cls_dict['_init_dict'] = dict 179 return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict) 180 181 # initialization 182 def __init__(cls, name, bases, dict): 183 super(MetaSimObject, cls).__init__(name, bases, dict) 184 185 # initialize required attributes 186 cls._params = multidict() 187 cls._values = multidict() 188 cls._anon_subclass_counter = 0 189 190 # We don't support multiple inheritance. If you want to, you 191 # must fix multidict to deal with it properly. 192 if len(bases) > 1: 193 raise TypeError, "SimObjects do not support multiple inheritance" 194 195 base = bases[0] 196 197 if isinstance(base, MetaSimObject): 198 cls._params.parent = base._params 199 cls._values.parent = base._values 200 201 # If your parent has a value in it that's a config node, clone 202 # it. Do this now so if we update any of the values' 203 # attributes we are updating the clone and not the original. 204 for key,val in base._values.iteritems(): 205 206 # don't clone if (1) we're about to overwrite it with 207 # a local setting or (2) we've already cloned a copy 208 # from an earlier (more derived) base 209 if cls._init_dict.has_key(key) or cls._values.has_key(key): 210 continue 211 212 if isSimObject(val): 213 cls._values[key] = val() 214 elif isSimObjSequence(val) and len(val): 215 cls._values[key] = [ v() for v in val ] 216 217 # now process remaining _init_dict items 218 for key,val in cls._init_dict.items(): 219 if isinstance(val, (types.FunctionType, types.TypeType)): 220 type.__setattr__(cls, key, val) 221 222 # param descriptions 223 elif isinstance(val, ParamDesc): 224 cls._new_param(key, val) 225 226 # init-time-only keywords 227 elif cls.init_keywords.has_key(key): 228 cls._set_keyword(key, val, cls.init_keywords[key]) 229 230 # default: use normal path (ends up in __setattr__) 231 else: 232 setattr(cls, key, val) 233 234 def _set_keyword(cls, keyword, val, kwtype): 235 if not isinstance(val, kwtype): 236 raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ 237 (keyword, type(val), kwtype) 238 if isinstance(val, types.FunctionType): 239 val = classmethod(val) 240 type.__setattr__(cls, keyword, val) 241 242 def _new_param(cls, name, value): 243 cls._params[name] = value 244 if hasattr(value, 'default'): 245 setattr(cls, name, value.default) 246 247 # Set attribute (called on foo.attr = value when foo is an 248 # instance of class cls). 249 def __setattr__(cls, attr, value): 250 # normal processing for private attributes 251 if attr.startswith('_'): 252 type.__setattr__(cls, attr, value) 253 return 254 255 if cls.keywords.has_key(attr): 256 cls._set_keyword(attr, value, cls.keywords[attr]) 257 return 258 259 # must be SimObject param 260 param = cls._params.get(attr, None) 261 if param: 262 # It's ok: set attribute by delegating to 'object' class. 263 try: 264 cls._values[attr] = param.convert(value) 265 except Exception, e: 266 msg = "%s\nError setting param %s.%s to %s\n" % \ 267 (e, cls.__name__, attr, value) 268 e.args = (msg, ) 269 raise 270 # I would love to get rid of this 271 elif isSimObject(value) or isSimObjSequence(value): 272 cls._values[attr] = value 273 else: 274 raise AttributeError, \ 275 "Class %s has no parameter %s" % (cls.__name__, attr) 276 277 def __getattr__(cls, attr): 278 if cls._values.has_key(attr): 279 return cls._values[attr] 280 281 raise AttributeError, \ 282 "object '%s' has no attribute '%s'" % (cls.__name__, attr) 283 284# The ConfigNode class is the root of the special hierarchy. Most of 285# the code in this class deals with the configuration hierarchy itself 286# (parent/child node relationships). 287class SimObject(object): 288 # Specify metaclass. Any class inheriting from SimObject will 289 # get this metaclass. 290 __metaclass__ = MetaSimObject 291 292 def __init__(self, _value_parent = None, **kwargs): 293 self._children = {} 294 if _value_parent and type(_value_parent) != type(self): 295 # this was called as a type conversion rather than a clone 296 raise TypeError, "Cannot convert %s to %s" % \ 297 (_value_parent.__class__.__name__, self.__class__.__name__) 298 if not _value_parent: 299 _value_parent = self.__class__ 300 # clone values 301 self._values = multidict(_value_parent._values) 302 for key,val in _value_parent._values.iteritems(): 303 if isSimObject(val): 304 setattr(self, key, val()) 305 elif isSimObjSequence(val) and len(val): 306 setattr(self, key, [ v() for v in val ]) 307 # apply attribute assignments from keyword args, if any 308 for key,val in kwargs.iteritems(): 309 setattr(self, key, val) 310 311 def __call__(self, **kwargs): 312 return self.__class__(_value_parent = self, **kwargs) 313 314 def __getattr__(self, attr): 315 if self._values.has_key(attr): 316 return self._values[attr] 317 318 raise AttributeError, "object '%s' has no attribute '%s'" \ 319 % (self.__class__.__name__, attr) 320 321 # Set attribute (called on foo.attr = value when foo is an 322 # instance of class cls). 323 def __setattr__(self, attr, value): 324 # normal processing for private attributes 325 if attr.startswith('_'): 326 object.__setattr__(self, attr, value) 327 return 328 329 # must be SimObject param 330 param = self._params.get(attr, None) 331 if param: 332 # It's ok: set attribute by delegating to 'object' class. 333 try: 334 value = param.convert(value) 335 except Exception, e: 336 msg = "%s\nError setting param %s.%s to %s\n" % \ 337 (e, self.__class__.__name__, attr, value) 338 e.args = (msg, ) 339 raise 340 # I would love to get rid of this 341 elif isSimObject(value) or isSimObjSequence(value): 342 pass 343 else: 344 raise AttributeError, "Class %s has no parameter %s" \ 345 % (self.__class__.__name__, attr) 346 347 # clear out old child with this name, if any 348 self.clear_child(attr) 349 350 if isSimObject(value): 351 value.set_path(self, attr) 352 elif isSimObjSequence(value): 353 value = SimObjVector(value) 354 [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)] 355 356 self._values[attr] = value 357 358 # this hack allows tacking a '[0]' onto parameters that may or may 359 # not be vectors, and always getting the first element (e.g. cpus) 360 def __getitem__(self, key): 361 if key == 0: 362 return self 363 raise TypeError, "Non-zero index '%s' to SimObject" % key 364 365 # clear out children with given name, even if it's a vector 366 def clear_child(self, name): 367 if not self._children.has_key(name): 368 return 369 child = self._children[name] 370 if isinstance(child, SimObjVector): 371 for i in xrange(len(child)): 372 del self._children["s%d" % (name, i)] 373 del self._children[name] 374 375 def add_child(self, name, value): 376 self._children[name] = value 377 378 def set_path(self, parent, name): 379 if not hasattr(self, '_parent'): 380 self._parent = parent 381 self._name = name 382 parent.add_child(name, self) 383 384 def path(self): 385 if not hasattr(self, '_parent'): 386 return 'root' 387 ppath = self._parent.path() 388 if ppath == 'root': 389 return self._name 390 return ppath + "." + self._name 391 392 def __str__(self): 393 return self.path() 394 395 def ini_str(self): 396 return self.path() 397 398 def find_any(self, ptype): 399 if isinstance(self, ptype): 400 return self, True 401 402 found_obj = None 403 for child in self._children.itervalues(): 404 if isinstance(child, ptype): 405 if found_obj != None and child != found_obj: 406 raise AttributeError, \ 407 'parent.any matched more than one: %s %s' % \ 408 (found_obj.path, child.path) 409 found_obj = child 410 # search param space 411 for pname,pdesc in self._params.iteritems(): 412 if issubclass(pdesc.ptype, ptype): 413 match_obj = self._values[pname] 414 if found_obj != None and found_obj != match_obj: 415 raise AttributeError, \ 416 'parent.any matched more than one: %s' % obj.path 417 found_obj = match_obj 418 return found_obj, found_obj != None 419 420 def print_ini(self): 421 print '[' + self.path() + ']' # .ini section header 422 423 if hasattr(self, 'type') and not isinstance(self, ParamContext): 424 print 'type=%s' % self.type 425 426 child_names = self._children.keys() 427 child_names.sort() 428 np_child_names = [c for c in child_names \ 429 if not isinstance(self._children[c], ParamContext)] 430 if len(np_child_names): 431 print 'children=%s' % ' '.join(np_child_names) 432 433 param_names = self._params.keys() 434 param_names.sort() 435 for param in param_names: 436 value = self._values.get(param, None) 437 if value != None: 438 if isproxy(value): 439 try: 440 value = value.unproxy(self) 441 except: 442 print >> sys.stderr, \ 443 "Error in unproxying param '%s' of %s" % \ 444 (param, self.path()) 445 raise 446 setattr(self, param, value) 447 print '%s=%s' % (param, self._values[param].ini_str()) 448 449 print # blank line between objects 450 451 for child in child_names: 452 self._children[child].print_ini() 453 454 # generate output file for 'dot' to display as a pretty graph. 455 # this code is currently broken. 456 def outputDot(self, dot): 457 label = "{%s|" % self.path 458 if isSimObject(self.realtype): 459 label += '%s|' % self.type 460 461 if self.children: 462 # instantiate children in same order they were added for 463 # backward compatibility (else we can end up with cpu1 464 # before cpu0). 465 for c in self.children: 466 dot.add_edge(pydot.Edge(self.path,c.path, style="bold")) 467 468 simobjs = [] 469 for param in self.params: 470 try: 471 if param.value is None: 472 raise AttributeError, 'Parameter with no value' 473 474 value = param.value 475 string = param.string(value) 476 except Exception, e: 477 msg = 'exception in %s:%s\n%s' % (self.name, param.name, e) 478 e.args = (msg, ) 479 raise 480 481 if isSimObject(param.ptype) and string != "Null": 482 simobjs.append(string) 483 else: 484 label += '%s = %s\\n' % (param.name, string) 485 486 for so in simobjs: 487 label += "|<%s> %s" % (so, so) 488 dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, 489 tailport="w")) 490 label += '}' 491 dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) 492 493 # recursively dump out children 494 for c in self.children: 495 c.outputDot(dot) 496 497class ParamContext(SimObject): 498 pass 499 500##################################################################### 501# 502# Proxy object support. 503# 504##################################################################### 505 506class BaseProxy(object): 507 def __init__(self, search_self, search_up): 508 self._search_self = search_self 509 self._search_up = search_up 510 self._multiplier = None 511 512 def __setattr__(self, attr, value): 513 if not attr.startswith('_'): 514 raise AttributeError, 'cannot set attribute on proxy object' 515 super(BaseProxy, self).__setattr__(attr, value) 516 517 # support multiplying proxies by constants 518 def __mul__(self, other): 519 if not isinstance(other, (int, float)): 520 raise TypeError, "Proxy multiplier must be integer" 521 if self._multiplier == None: 522 self._multiplier = other 523 else: 524 # support chained multipliers 525 self._multiplier *= other 526 return self 527 528 __rmul__ = __mul__ 529 530 def _mulcheck(self, result): 531 if self._multiplier == None: 532 return result 533 return result * self._multiplier 534 535 def unproxy(self, base): 536 obj = base 537 done = False 538 539 if self._search_self: 540 result, done = self.find(obj) 541 542 if self._search_up: 543 while not done: 544 try: obj = obj._parent 545 except: break 546 547 result, done = self.find(obj) 548 549 if not done: 550 raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \ 551 (self.path(), base.path()) 552 553 if isinstance(result, BaseProxy): 554 if result == self: 555 raise RuntimeError, "Cycle in unproxy" 556 result = result.unproxy(obj) 557 558 return self._mulcheck(result) 559 560 def getindex(obj, index): 561 if index == None: 562 return obj 563 try: 564 obj = obj[index] 565 except TypeError: 566 if index != 0: 567 raise 568 # if index is 0 and item is not subscriptable, just 569 # use item itself (so cpu[0] works on uniprocessors) 570 return obj 571 getindex = staticmethod(getindex) 572 573 def set_param_desc(self, pdesc): 574 self._pdesc = pdesc 575 576class AttrProxy(BaseProxy): 577 def __init__(self, search_self, search_up, attr): 578 super(AttrProxy, self).__init__(search_self, search_up) 579 self._attr = attr 580 self._modifiers = [] 581 582 def __getattr__(self, attr): 583 # python uses __bases__ internally for inheritance 584 if attr.startswith('_'): 585 return super(AttrProxy, self).__getattr__(self, attr) 586 if hasattr(self, '_pdesc'): 587 raise AttributeError, "Attribute reference on bound proxy" 588 self._modifiers.append(attr) 589 return self 590 591 # support indexing on proxies (e.g., Self.cpu[0]) 592 def __getitem__(self, key): 593 if not isinstance(key, int): 594 raise TypeError, "Proxy object requires integer index" 595 self._modifiers.append(key) 596 return self 597 598 def find(self, obj): 599 try: 600 val = getattr(obj, self._attr) 601 except: 602 return None, False 603 while isproxy(val): 604 val = val.unproxy(obj) 605 for m in self._modifiers: 606 if isinstance(m, str): 607 val = getattr(val, m) 608 elif isinstance(m, int): 609 val = val[m] 610 else: 611 assert("Item must be string or integer") 612 while isproxy(val): 613 val = val.unproxy(obj) 614 return val, True 615 616 def path(self): 617 p = self._attr 618 for m in self._modifiers: 619 if isinstance(m, str): 620 p += '.%s' % m 621 elif isinstance(m, int): 622 p += '[%d]' % m 623 else: 624 assert("Item must be string or integer") 625 return p 626 627class AnyProxy(BaseProxy): 628 def find(self, obj): 629 return obj.find_any(self._pdesc.ptype) 630 631 def path(self): 632 return 'any' 633 634def isproxy(obj): 635 if isinstance(obj, BaseProxy): 636 return True 637 elif isinstance(obj, (list, tuple)): 638 for v in obj: 639 if isproxy(v): 640 return True 641 return False 642 643class ProxyFactory(object): 644 def __init__(self, search_self, search_up): 645 self.search_self = search_self 646 self.search_up = search_up 647 648 def __getattr__(self, attr): 649 if attr == 'any': 650 return AnyProxy(self.search_self, self.search_up) 651 else: 652 return AttrProxy(self.search_self, self.search_up, attr) 653 654# global objects for handling proxies 655Parent = ProxyFactory(search_self = False, search_up = True) 656Self = ProxyFactory(search_self = True, search_up = False) 657 658##################################################################### 659# 660# Parameter description classes 661# 662# The _params dictionary in each class maps parameter names to 663# either a Param or a VectorParam object. These objects contain the 664# parameter description string, the parameter type, and the default 665# value (loaded from the PARAM section of the .odesc files). The 666# _convert() method on these objects is used to force whatever value 667# is assigned to the parameter to the appropriate type. 668# 669# Note that the default values are loaded into the class's attribute 670# space when the parameter dictionary is initialized (in 671# MetaConfigNode._setparams()); after that point they aren't used. 672# 673##################################################################### 674 675# Dummy base class to identify types that are legitimate for SimObject 676# parameters. 677class ParamValue(object): 678 679 # default for printing to .ini file is regular string conversion. 680 # will be overridden in some cases 681 def ini_str(self): 682 return str(self) 683 684 # allows us to blithely call unproxy() on things without checking 685 # if they're really proxies or not 686 def unproxy(self, base): 687 return self 688 689# Regular parameter description. 690class ParamDesc(object): 691 def __init__(self, ptype_str, ptype, *args, **kwargs): 692 self.ptype_str = ptype_str 693 # remember ptype only if it is provided 694 if ptype != None: 695 self.ptype = ptype 696 697 if args: 698 if len(args) == 1: 699 self.desc = args[0] 700 elif len(args) == 2: 701 self.default = args[0] 702 self.desc = args[1] 703 else: 704 raise TypeError, 'too many arguments' 705 706 if kwargs.has_key('desc'): 707 assert(not hasattr(self, 'desc')) 708 self.desc = kwargs['desc'] 709 del kwargs['desc'] 710 711 if kwargs.has_key('default'): 712 assert(not hasattr(self, 'default')) 713 self.default = kwargs['default'] 714 del kwargs['default'] 715 716 if kwargs: 717 raise TypeError, 'extra unknown kwargs %s' % kwargs 718 719 if not hasattr(self, 'desc'): 720 raise TypeError, 'desc attribute missing' 721 722 def __getattr__(self, attr): 723 if attr == 'ptype': 724 try: 725 ptype = eval(self.ptype_str, m5.__dict__) 726 if not isinstance(ptype, type): 727 panic("Param qualifier is not a type: %s" % self.ptype) 728 self.ptype = ptype 729 return ptype 730 except NameError: 731 pass 732 raise AttributeError, "'%s' object has no attribute '%s'" % \ 733 (type(self).__name__, attr) 734 735 def convert(self, value): 736 if isinstance(value, BaseProxy): 737 value.set_param_desc(self) 738 return value 739 if not hasattr(self, 'ptype') and isNullPointer(value): 740 # deferred evaluation of SimObject; continue to defer if 741 # we're just assigning a null pointer 742 return value 743 if isinstance(value, self.ptype): 744 return value 745 if isNullPointer(value) and issubclass(self.ptype, SimObject): 746 return value 747 return self.ptype(value) 748 749# Vector-valued parameter description. Just like ParamDesc, except 750# that the value is a vector (list) of the specified type instead of a 751# single value. 752 753class VectorParamValue(list): 754 def ini_str(self): 755 return ' '.join([str(v) for v in self]) 756 757 def unproxy(self, base): 758 return [v.unproxy(base) for v in self] 759 760class SimObjVector(VectorParamValue): 761 def print_ini(self): 762 for v in self: 763 v.print_ini() 764 765class VectorParamDesc(ParamDesc): 766 # Convert assigned value to appropriate type. If the RHS is not a 767 # list or tuple, it generates a single-element list. 768 def convert(self, value): 769 if isinstance(value, (list, tuple)): 770 # list: coerce each element into new list 771 tmp_list = [ ParamDesc.convert(self, v) for v in value ] 772 if isSimObjSequence(tmp_list): 773 return SimObjVector(tmp_list) 774 else: 775 return VectorParamValue(tmp_list) 776 else: 777 # singleton: leave it be (could coerce to a single-element 778 # list here, but for some historical reason we don't... 779 return ParamDesc.convert(self, value) 780 781 782class ParamFactory(object): 783 def __init__(self, param_desc_class, ptype_str = None): 784 self.param_desc_class = param_desc_class 785 self.ptype_str = ptype_str 786 787 def __getattr__(self, attr): 788 if self.ptype_str: 789 attr = self.ptype_str + '.' + attr 790 return ParamFactory(self.param_desc_class, attr) 791 792 # E.g., Param.Int(5, "number of widgets") 793 def __call__(self, *args, **kwargs): 794 caller_frame = inspect.stack()[1][0] 795 ptype = None 796 try: 797 ptype = eval(self.ptype_str, 798 caller_frame.f_globals, caller_frame.f_locals) 799 if not isinstance(ptype, type): 800 raise TypeError, \ 801 "Param qualifier is not a type: %s" % ptype 802 except NameError: 803 # if name isn't defined yet, assume it's a SimObject, and 804 # try to resolve it later 805 pass 806 return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs) 807 808Param = ParamFactory(ParamDesc) 809VectorParam = ParamFactory(VectorParamDesc) 810 811##################################################################### 812# 813# Parameter Types 814# 815# Though native Python types could be used to specify parameter types 816# (the 'ptype' field of the Param and VectorParam classes), it's more 817# flexible to define our own set of types. This gives us more control 818# over how Python expressions are converted to values (via the 819# __init__() constructor) and how these values are printed out (via 820# the __str__() conversion method). Eventually we'll need these types 821# to correspond to distinct C++ types as well. 822# 823##################################################################### 824 825class Range(ParamValue): 826 type = int # default; can be overridden in subclasses 827 def __init__(self, *args, **kwargs): 828 829 def handle_kwargs(self, kwargs): 830 if 'end' in kwargs: 831 self.second = self.type(kwargs.pop('end')) 832 elif 'size' in kwargs: 833 self.second = self.first + self.type(kwargs.pop('size')) - 1 834 else: 835 raise TypeError, "Either end or size must be specified" 836 837 if len(args) == 0: 838 self.first = self.type(kwargs.pop('start')) 839 handle_kwargs(self, kwargs) 840 841 elif len(args) == 1: 842 if kwargs: 843 self.first = self.type(args[0]) 844 handle_kwargs(self, kwargs) 845 elif isinstance(args[0], Range): 846 self.first = self.type(args[0].first) 847 self.second = self.type(args[0].second) 848 else: 849 self.first = self.type(0) 850 self.second = self.type(args[0]) - 1 851 852 elif len(args) == 2: 853 self.first = self.type(args[0]) 854 self.second = self.type(args[1]) 855 else: 856 raise TypeError, "Too many arguments specified" 857 858 if kwargs: 859 raise TypeError, "too many keywords: %s" % kwargs.keys() 860 861 def __str__(self): 862 return '%s:%s' % (self.first, self.second) 863 864# Metaclass for bounds-checked integer parameters. See CheckedInt. 865class CheckedIntType(type): 866 def __init__(cls, name, bases, dict): 867 super(CheckedIntType, cls).__init__(name, bases, dict) 868 869 # CheckedInt is an abstract base class, so we actually don't 870 # want to do any processing on it... the rest of this code is 871 # just for classes that derive from CheckedInt. 872 if name == 'CheckedInt': 873 return 874 875 if not (hasattr(cls, 'min') and hasattr(cls, 'max')): 876 if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): 877 panic("CheckedInt subclass %s must define either\n" \ 878 " 'min' and 'max' or 'size' and 'unsigned'\n" \ 879 % name); 880 if cls.unsigned: 881 cls.min = 0 882 cls.max = 2 ** cls.size - 1 883 else: 884 cls.min = -(2 ** (cls.size - 1)) 885 cls.max = (2 ** (cls.size - 1)) - 1 886 887# Abstract superclass for bounds-checked integer parameters. This 888# class is subclassed to generate parameter classes with specific 889# bounds. Initialization of the min and max bounds is done in the 890# metaclass CheckedIntType.__init__. 891class CheckedInt(long,ParamValue): 892 __metaclass__ = CheckedIntType 893 894 def __new__(cls, value): 895 if isinstance(value, str): 896 value = toInteger(value) 897 898 self = long.__new__(cls, value) 899 900 if not cls.min <= self <= cls.max: 901 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ 902 (cls.min, self, cls.max) 903 return self 904 905class Int(CheckedInt): size = 32; unsigned = False 906class Unsigned(CheckedInt): size = 32; unsigned = True 907 908class Int8(CheckedInt): size = 8; unsigned = False 909class UInt8(CheckedInt): size = 8; unsigned = True 910class Int16(CheckedInt): size = 16; unsigned = False 911class UInt16(CheckedInt): size = 16; unsigned = True 912class Int32(CheckedInt): size = 32; unsigned = False 913class UInt32(CheckedInt): size = 32; unsigned = True 914class Int64(CheckedInt): size = 64; unsigned = False 915class UInt64(CheckedInt): size = 64; unsigned = True 916 917class Counter(CheckedInt): size = 64; unsigned = True 918class Tick(CheckedInt): size = 64; unsigned = True 919class TcpPort(CheckedInt): size = 16; unsigned = True 920class UdpPort(CheckedInt): size = 16; unsigned = True 921 922class Percent(CheckedInt): min = 0; max = 100 923 924class Float(ParamValue, float): 925 pass 926 927class MemorySize(CheckedInt): 928 size = 64 929 unsigned = True 930 def __new__(cls, value): 931 return super(MemorySize, cls).__new__(cls, toMemorySize(value)) 932 933 934class Addr(CheckedInt): 935 size = 64 936 unsigned = True 937 def __new__(cls, value): 938 try: 939 value = long(toMemorySize(value)) 940 except TypeError: 941 value = long(value) 942 return super(Addr, cls).__new__(cls, value) 943 944class AddrRange(Range): 945 type = Addr 946 947# String-valued parameter. Just mixin the ParamValue class 948# with the built-in str class. 949class String(ParamValue,str): 950 pass 951 952# Boolean parameter type. Python doesn't let you subclass bool, since 953# it doesn't want to let you create multiple instances of True and 954# False. Thus this is a little more complicated than String. 955class Bool(ParamValue): 956 def __init__(self, value): 957 try: 958 self.value = toBool(value) 959 except TypeError: 960 self.value = bool(value) 961 962 def __str__(self): 963 return str(self.value) 964 965 def ini_str(self): 966 if self.value: 967 return 'true' 968 return 'false' 969 970def IncEthernetAddr(addr, val = 1): 971 bytes = map(lambda x: int(x, 16), addr.split(':')) 972 bytes[5] += val 973 for i in (5, 4, 3, 2, 1): 974 val,rem = divmod(bytes[i], 256) 975 bytes[i] = rem 976 if val == 0: 977 break 978 bytes[i - 1] += val 979 assert(bytes[0] <= 255) 980 return ':'.join(map(lambda x: '%02x' % x, bytes)) 981 982class NextEthernetAddr(object): 983 __metaclass__ = Singleton 984 addr = "00:90:00:00:00:01" 985 986 def __init__(self, inc = 1): 987 self.value = self.addr 988 self.addr = IncEthernetAddr(self.addr, inc) 989 990class EthernetAddr(ParamValue): 991 def __init__(self, value): 992 if value == NextEthernetAddr: 993 self.value = value 994 return 995 996 if not isinstance(value, str): 997 raise TypeError, "expected an ethernet address and didn't get one" 998 999 bytes = value.split(':') 1000 if len(bytes) != 6: 1001 raise TypeError, 'invalid ethernet address %s' % value 1002 1003 for byte in bytes: 1004 if not 0 <= int(byte) <= 256: 1005 raise TypeError, 'invalid ethernet address %s' % value 1006 1007 self.value = value 1008 1009 def __str__(self): 1010 if self.value == NextEthernetAddr: 1011 self.value = self.value().value 1012 return self.value 1013 1014# Special class for NULL pointers. Note the special check in 1015# make_param_value() above that lets these be assigned where a 1016# SimObject is required. 1017# only one copy of a particular node 1018class NullSimObject(object): 1019 __metaclass__ = Singleton 1020 1021 def __call__(cls): 1022 return cls 1023 1024 def _instantiate(self, parent = None, path = ''): 1025 pass 1026 1027 def ini_str(self): 1028 return 'Null' 1029 1030 def unproxy(self,base): 1031 return self 1032 1033 def set_path(self, parent, name): 1034 pass 1035 def __str__(self): 1036 return 'Null' 1037 1038# The only instance you'll ever need... 1039Null = NULL = NullSimObject() 1040 1041# Enumerated types are a little more complex. The user specifies the 1042# type as Enum(foo) where foo is either a list or dictionary of 1043# alternatives (typically strings, but not necessarily so). (In the 1044# long run, the integer value of the parameter will be the list index 1045# or the corresponding dictionary value. For now, since we only check 1046# that the alternative is valid and then spit it into a .ini file, 1047# there's not much point in using the dictionary.) 1048 1049# What Enum() must do is generate a new type encapsulating the 1050# provided list/dictionary so that specific values of the parameter 1051# can be instances of that type. We define two hidden internal 1052# classes (_ListEnum and _DictEnum) to serve as base classes, then 1053# derive the new type from the appropriate base class on the fly. 1054 1055 1056# Metaclass for Enum types 1057class MetaEnum(type): 1058 def __init__(cls, name, bases, init_dict): 1059 if init_dict.has_key('map'): 1060 if not isinstance(cls.map, dict): 1061 raise TypeError, "Enum-derived class attribute 'map' " \ 1062 "must be of type dict" 1063 # build list of value strings from map 1064 cls.vals = cls.map.keys() 1065 cls.vals.sort() 1066 elif init_dict.has_key('vals'): 1067 if not isinstance(cls.vals, list): 1068 raise TypeError, "Enum-derived class attribute 'vals' " \ 1069 "must be of type list" 1070 # build string->value map from vals sequence 1071 cls.map = {} 1072 for idx,val in enumerate(cls.vals): 1073 cls.map[val] = idx 1074 else: 1075 raise TypeError, "Enum-derived class must define "\ 1076 "attribute 'map' or 'vals'" 1077 1078 super(MetaEnum, cls).__init__(name, bases, init_dict) 1079 1080 def cpp_declare(cls): 1081 s = 'enum %s {\n ' % cls.__name__ 1082 s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) 1083 s += '\n};\n' 1084 return s 1085 1086# Base class for enum types. 1087class Enum(ParamValue): 1088 __metaclass__ = MetaEnum 1089 vals = [] 1090 1091 def __init__(self, value): 1092 if value not in self.map: 1093 raise TypeError, "Enum param got bad value '%s' (not in %s)" \ 1094 % (value, self.vals) 1095 self.value = value 1096 1097 def __str__(self): 1098 return self.value 1099 1100ticks_per_sec = None 1101 1102# how big does a rounding error need to be before we warn about it? 1103frequency_tolerance = 0.001 # 0.1% 1104 1105# convert a floting-point # of ticks to integer, and warn if rounding 1106# discards too much precision 1107def tick_check(float_ticks): 1108 if float_ticks == 0: 1109 return 0 1110 int_ticks = int(round(float_ticks)) 1111 err = (float_ticks - int_ticks) / float_ticks 1112 if err > frequency_tolerance: 1113 print >> sys.stderr, "Warning: rounding error > tolerance" 1114 print >> sys.stderr, " %f rounded to %d" % (float_ticks, int_ticks) 1115 #raise ValueError 1116 return int_ticks 1117 1118# superclass for "numeric" parameter values, to emulate math 1119# operations in a type-safe way. e.g., a Latency times an int returns 1120# a new Latency object. 1121class NumericParamValue(ParamValue): 1122 def __str__(self): 1123 return str(self.value) 1124 1125 def __float__(self): 1126 return float(self.value) 1127 1128 def __mul__(self, other): 1129 newobj = self.__class__(self) 1130 newobj.value *= other 1131 return newobj 1132 1133 __rmul__ = __mul__ 1134 1135 def __div__(self, other): 1136 newobj = self.__class__(self) 1137 newobj.value /= other 1138 return newobj 1139 1140 1141def getLatency(value): 1142 if isinstance(value, Latency) or isinstance(value, Clock): 1143 return value.value 1144 elif isinstance(value, Frequency) or isinstance(value, RootClock): 1145 return 1 / value.value 1146 elif isinstance(value, str): 1147 try: 1148 return toLatency(value) 1149 except ValueError: 1150 try: 1151 return 1 / toFrequency(value) 1152 except ValueError: 1153 pass # fall through 1154 raise ValueError, "Invalid Frequency/Latency value '%s'" % value 1155 1156 1157class Latency(NumericParamValue): 1158 def __init__(self, value): 1159 self.value = getLatency(value) 1160 1161 def __getattr__(self, attr): 1162 if attr in ('latency', 'period'): 1163 return self 1164 if attr == 'frequency': 1165 return Frequency(self) 1166 raise AttributeError, "Latency object has no attribute '%s'" % attr 1167 1168 # convert latency to ticks 1169 def ini_str(self): 1170 return str(tick_check(self.value * ticks_per_sec)) 1171 1172class Frequency(NumericParamValue): 1173 def __init__(self, value): 1174 self.value = 1 / getLatency(value) 1175 1176 def __getattr__(self, attr): 1177 if attr == 'frequency': 1178 return self 1179 if attr in ('latency', 'period'): 1180 return Latency(self) 1181 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1182 1183 # convert frequency to ticks per period 1184 def ini_str(self): 1185 return self.period.ini_str() 1186 1187# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz). 1188# We can't inherit from Frequency because we don't want it to be directly 1189# assignable to a regular Frequency parameter. 1190class RootClock(ParamValue): 1191 def __init__(self, value): 1192 self.value = 1 / getLatency(value) 1193 1194 def __getattr__(self, attr): 1195 if attr == 'frequency': 1196 return Frequency(self) 1197 if attr in ('latency', 'period'): 1198 return Latency(self) 1199 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1200 1201 def ini_str(self): 1202 return str(tick_check(self.value)) 1203 1204# A generic frequency and/or Latency value. Value is stored as a latency, 1205# but to avoid ambiguity this object does not support numeric ops (* or /). 1206# An explicit conversion to a Latency or Frequency must be made first. 1207class Clock(ParamValue): 1208 def __init__(self, value): 1209 self.value = getLatency(value) 1210 1211 def __getattr__(self, attr): 1212 if attr == 'frequency': 1213 return Frequency(self) 1214 if attr in ('latency', 'period'): 1215 return Latency(self) 1216 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1217 1218 def ini_str(self): 1219 return self.period.ini_str() 1220 1221class NetworkBandwidth(float,ParamValue): 1222 def __new__(cls, value): 1223 val = toNetworkBandwidth(value) / 8.0 1224 return super(cls, NetworkBandwidth).__new__(cls, val) 1225 1226 def __str__(self): 1227 return str(self.val) 1228 1229 def ini_str(self): 1230 return '%f' % (ticks_per_sec / float(self)) 1231 1232class MemoryBandwidth(float,ParamValue): 1233 def __new__(self, value): 1234 val = toMemoryBandwidth(value) 1235 return super(cls, MemoryBandwidth).__new__(cls, val) 1236 1237 def __str__(self): 1238 return str(self.val) 1239 1240 def ini_str(self): 1241 return '%f' % (ticks_per_sec / float(self)) 1242 1243# 1244# "Constants"... handy aliases for various values. 1245# 1246 1247# Some memory range specifications use this as a default upper bound. 1248MaxAddr = Addr.max 1249MaxTick = Tick.max 1250AllMemory = AddrRange(0, MaxAddr) 1251 1252##################################################################### 1253 1254# The final hook to generate .ini files. Called from configuration 1255# script once config is built. 1256def instantiate(root): 1257 global ticks_per_sec 1258 ticks_per_sec = float(root.clock.frequency) 1259 root.print_ini() 1260 noDot = True # temporary until we fix dot 1261 if not noDot: 1262 dot = pydot.Dot() 1263 instance.outputDot(dot) 1264 dot.orientation = "portrait" 1265 dot.size = "8.5,11" 1266 dot.ranksep="equally" 1267 dot.rank="samerank" 1268 dot.write("config.dot") 1269 dot.write_ps("config.ps") 1270 1271# __all__ defines the list of symbols that get exported when 1272# 'from config import *' is invoked. Try to keep this reasonably 1273# short to avoid polluting other namespaces. 1274__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam', 1275 'Parent', 'Self', 1276 'Enum', 'Bool', 'String', 'Float', 1277 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', 1278 'Int32', 'UInt32', 'Int64', 'UInt64', 1279 'Counter', 'Addr', 'Tick', 'Percent', 1280 'TcpPort', 'UdpPort', 'EthernetAddr', 1281 'MemorySize', 'Latency', 'Frequency', 'RootClock', 'Clock', 1282 'NetworkBandwidth', 'MemoryBandwidth', 1283 'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory', 1284 'Null', 'NULL', 1285 'NextEthernetAddr', 'instantiate'] 1286 1287