SimObject.py revision 1693
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 not _value_parent: 295 _value_parent = self.__class__ 296 # clone values 297 self._values = multidict(_value_parent._values) 298 for key,val in _value_parent._values.iteritems(): 299 if isSimObject(val): 300 setattr(self, key, val()) 301 elif isSimObjSequence(val) and len(val): 302 setattr(self, key, [ v() for v in val ]) 303 # apply attribute assignments from keyword args, if any 304 for key,val in kwargs.iteritems(): 305 setattr(self, key, val) 306 307 def __call__(self, **kwargs): 308 return self.__class__(_value_parent = self, **kwargs) 309 310 def __getattr__(self, attr): 311 if self._values.has_key(attr): 312 return self._values[attr] 313 314 raise AttributeError, "object '%s' has no attribute '%s'" \ 315 % (self.__class__.__name__, attr) 316 317 # Set attribute (called on foo.attr = value when foo is an 318 # instance of class cls). 319 def __setattr__(self, attr, value): 320 # normal processing for private attributes 321 if attr.startswith('_'): 322 object.__setattr__(self, attr, value) 323 return 324 325 # must be SimObject param 326 param = self._params.get(attr, None) 327 if param: 328 # It's ok: set attribute by delegating to 'object' class. 329 try: 330 value = param.convert(value) 331 except Exception, e: 332 msg = "%s\nError setting param %s.%s to %s\n" % \ 333 (e, self.__class__.__name__, attr, value) 334 e.args = (msg, ) 335 raise 336 # I would love to get rid of this 337 elif isSimObject(value) or isSimObjSequence(value): 338 pass 339 else: 340 raise AttributeError, "Class %s has no parameter %s" \ 341 % (self.__class__.__name__, attr) 342 343 # clear out old child with this name, if any 344 self.clear_child(attr) 345 346 if isSimObject(value): 347 value.set_path(self, attr) 348 elif isSimObjSequence(value): 349 value = SimObjVector(value) 350 [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)] 351 352 self._values[attr] = value 353 354 # this hack allows tacking a '[0]' onto parameters that may or may 355 # not be vectors, and always getting the first element (e.g. cpus) 356 def __getitem__(self, key): 357 if key == 0: 358 return self 359 raise TypeError, "Non-zero index '%s' to SimObject" % key 360 361 # clear out children with given name, even if it's a vector 362 def clear_child(self, name): 363 if not self._children.has_key(name): 364 return 365 child = self._children[name] 366 if isinstance(child, SimObjVector): 367 for i in xrange(len(child)): 368 del self._children["s%d" % (name, i)] 369 del self._children[name] 370 371 def add_child(self, name, value): 372 self._children[name] = value 373 374 def set_path(self, parent, name): 375 if not hasattr(self, '_parent'): 376 self._parent = parent 377 self._name = name 378 parent.add_child(name, self) 379 380 def path(self): 381 if not hasattr(self, '_parent'): 382 return 'root' 383 ppath = self._parent.path() 384 if ppath == 'root': 385 return self._name 386 return ppath + "." + self._name 387 388 def __str__(self): 389 return self.path() 390 391 def ini_str(self): 392 return self.path() 393 394 def find_any(self, ptype): 395 if isinstance(self, ptype): 396 return self, True 397 398 found_obj = None 399 for child in self._children.itervalues(): 400 if isinstance(child, ptype): 401 if found_obj != None and child != found_obj: 402 raise AttributeError, \ 403 'parent.any matched more than one: %s %s' % \ 404 (obj.path, child.path) 405 found_obj = child 406 # search param space 407 for pname,pdesc in self._params.iteritems(): 408 if issubclass(pdesc.ptype, ptype): 409 match_obj = self._values[pname] 410 if found_obj != None and found_obj != match_obj: 411 raise AttributeError, \ 412 'parent.any matched more than one: %s' % obj.path 413 found_obj = match_obj 414 return found_obj, found_obj != None 415 416 def print_ini(self): 417 print '[' + self.path() + ']' # .ini section header 418 419 if hasattr(self, 'type') and not isinstance(self, ParamContext): 420 print 'type =', self.type 421 422 child_names = self._children.keys() 423 child_names.sort() 424 np_child_names = [c for c in child_names \ 425 if not isinstance(self._children[c], ParamContext)] 426 if len(np_child_names): 427 print 'children =', ' '.join(np_child_names) 428 429 param_names = self._params.keys() 430 param_names.sort() 431 for param in param_names: 432 value = self._values.get(param, None) 433 if value != None: 434 if isproxy(value): 435 try: 436 value = value.unproxy(self) 437 except: 438 print >> sys.stderr, \ 439 "Error in unproxying param '%s' of %s" % \ 440 (param, self.path()) 441 raise 442 setattr(self, param, value) 443 print param, '=', self._values[param].ini_str() 444 445 print # blank line between objects 446 447 for child in child_names: 448 self._children[child].print_ini() 449 450 # generate output file for 'dot' to display as a pretty graph. 451 # this code is currently broken. 452 def outputDot(self, dot): 453 label = "{%s|" % self.path 454 if isSimObject(self.realtype): 455 label += '%s|' % self.type 456 457 if self.children: 458 # instantiate children in same order they were added for 459 # backward compatibility (else we can end up with cpu1 460 # before cpu0). 461 for c in self.children: 462 dot.add_edge(pydot.Edge(self.path,c.path, style="bold")) 463 464 simobjs = [] 465 for param in self.params: 466 try: 467 if param.value is None: 468 raise AttributeError, 'Parameter with no value' 469 470 value = param.value 471 string = param.string(value) 472 except Exception, e: 473 msg = 'exception in %s:%s\n%s' % (self.name, param.name, e) 474 e.args = (msg, ) 475 raise 476 477 if isSimObject(param.ptype) and string != "Null": 478 simobjs.append(string) 479 else: 480 label += '%s = %s\\n' % (param.name, string) 481 482 for so in simobjs: 483 label += "|<%s> %s" % (so, so) 484 dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, 485 tailport="w")) 486 label += '}' 487 dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) 488 489 # recursively dump out children 490 for c in self.children: 491 c.outputDot(dot) 492 493class ParamContext(SimObject): 494 pass 495 496##################################################################### 497# 498# Proxy object support. 499# 500##################################################################### 501 502class BaseProxy(object): 503 def __init__(self, search_self, search_up): 504 self._search_self = search_self 505 self._search_up = search_up 506 self._multiplier = None 507 508 def __setattr__(self, attr, value): 509 if not attr.startswith('_'): 510 raise AttributeError, 'cannot set attribute on proxy object' 511 super(BaseProxy, self).__setattr__(attr, value) 512 513 # support multiplying proxies by constants 514 def __mul__(self, other): 515 if not isinstance(other, (int, float)): 516 raise TypeError, "Proxy multiplier must be integer" 517 if self._multiplier == None: 518 self._multiplier = other 519 else: 520 # support chained multipliers 521 self._multiplier *= other 522 return self 523 524 __rmul__ = __mul__ 525 526 def _mulcheck(self, result): 527 if self._multiplier == None: 528 return result 529 return result * self._multiplier 530 531 def unproxy(self, base): 532 obj = base 533 done = False 534 535 if self._search_self: 536 result, done = self.find(obj) 537 538 if self._search_up: 539 while not done: 540 try: obj = obj._parent 541 except: break 542 543 result, done = self.find(obj) 544 545 if not done: 546 raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \ 547 (self.path(), base.path()) 548 549 if isinstance(result, BaseProxy): 550 if result == self: 551 raise RuntimeError, "Cycle in unproxy" 552 result = result.unproxy(obj) 553 554 return self._mulcheck(result) 555 556 def getindex(obj, index): 557 if index == None: 558 return obj 559 try: 560 obj = obj[index] 561 except TypeError: 562 if index != 0: 563 raise 564 # if index is 0 and item is not subscriptable, just 565 # use item itself (so cpu[0] works on uniprocessors) 566 return obj 567 getindex = staticmethod(getindex) 568 569 def set_param_desc(self, pdesc): 570 self._pdesc = pdesc 571 572class AttrProxy(BaseProxy): 573 def __init__(self, search_self, search_up, attr): 574 super(AttrProxy, self).__init__(search_self, search_up) 575 self._attr = attr 576 self._modifiers = [] 577 578 def __getattr__(self, attr): 579 # python uses __bases__ internally for inheritance 580 if attr.startswith('_'): 581 return super(AttrProxy, self).__getattr__(self, attr) 582 if hasattr(self, '_pdesc'): 583 raise AttributeError, "Attribute reference on bound proxy" 584 self._modifiers.append(attr) 585 return self 586 587 # support indexing on proxies (e.g., Self.cpu[0]) 588 def __getitem__(self, key): 589 if not isinstance(key, int): 590 raise TypeError, "Proxy object requires integer index" 591 self._modifiers.append(key) 592 return self 593 594 def find(self, obj): 595 try: 596 val = getattr(obj, self._attr) 597 except: 598 return None, False 599 while isproxy(val): 600 val = val.unproxy(obj) 601 for m in self._modifiers: 602 if isinstance(m, str): 603 val = getattr(val, m) 604 elif isinstance(m, int): 605 val = val[m] 606 else: 607 assert("Item must be string or integer") 608 while isproxy(val): 609 val = val.unproxy(obj) 610 return val, True 611 612 def path(self): 613 p = self._attr 614 for m in self._modifiers: 615 if isinstance(m, str): 616 p += '.%s' % m 617 elif isinstance(m, int): 618 p += '[%d]' % m 619 else: 620 assert("Item must be string or integer") 621 return p 622 623class AnyProxy(BaseProxy): 624 def find(self, obj): 625 return obj.find_any(self._pdesc.ptype) 626 627 def path(self): 628 return 'any' 629 630def isproxy(obj): 631 if isinstance(obj, BaseProxy): 632 return True 633 elif isinstance(obj, (list, tuple)): 634 for v in obj: 635 if isproxy(v): 636 return True 637 return False 638 639class ProxyFactory(object): 640 def __init__(self, search_self, search_up): 641 self.search_self = search_self 642 self.search_up = search_up 643 644 def __getattr__(self, attr): 645 if attr == 'any': 646 return AnyProxy(self.search_self, self.search_up) 647 else: 648 return AttrProxy(self.search_self, self.search_up, attr) 649 650# global objects for handling proxies 651Parent = ProxyFactory(search_self = False, search_up = True) 652Self = ProxyFactory(search_self = True, search_up = False) 653 654##################################################################### 655# 656# Parameter description classes 657# 658# The _params dictionary in each class maps parameter names to 659# either a Param or a VectorParam object. These objects contain the 660# parameter description string, the parameter type, and the default 661# value (loaded from the PARAM section of the .odesc files). The 662# _convert() method on these objects is used to force whatever value 663# is assigned to the parameter to the appropriate type. 664# 665# Note that the default values are loaded into the class's attribute 666# space when the parameter dictionary is initialized (in 667# MetaConfigNode._setparams()); after that point they aren't used. 668# 669##################################################################### 670 671# Dummy base class to identify types that are legitimate for SimObject 672# parameters. 673class ParamValue(object): 674 675 # default for printing to .ini file is regular string conversion. 676 # will be overridden in some cases 677 def ini_str(self): 678 return str(self) 679 680 # allows us to blithely call unproxy() on things without checking 681 # if they're really proxies or not 682 def unproxy(self, base): 683 return self 684 685# Regular parameter description. 686class ParamDesc(object): 687 def __init__(self, ptype_str, ptype, *args, **kwargs): 688 self.ptype_str = ptype_str 689 # remember ptype only if it is provided 690 if ptype != None: 691 self.ptype = ptype 692 693 if args: 694 if len(args) == 1: 695 self.desc = args[0] 696 elif len(args) == 2: 697 self.default = args[0] 698 self.desc = args[1] 699 else: 700 raise TypeError, 'too many arguments' 701 702 if kwargs.has_key('desc'): 703 assert(not hasattr(self, 'desc')) 704 self.desc = kwargs['desc'] 705 del kwargs['desc'] 706 707 if kwargs.has_key('default'): 708 assert(not hasattr(self, 'default')) 709 self.default = kwargs['default'] 710 del kwargs['default'] 711 712 if kwargs: 713 raise TypeError, 'extra unknown kwargs %s' % kwargs 714 715 if not hasattr(self, 'desc'): 716 raise TypeError, 'desc attribute missing' 717 718 def __getattr__(self, attr): 719 if attr == 'ptype': 720 try: 721 ptype = eval(self.ptype_str, m5.__dict__) 722 if not isinstance(ptype, type): 723 panic("Param qualifier is not a type: %s" % self.ptype) 724 self.ptype = ptype 725 return ptype 726 except NameError: 727 pass 728 raise AttributeError, "'%s' object has no attribute '%s'" % \ 729 (type(self).__name__, attr) 730 731 def convert(self, value): 732 if isinstance(value, BaseProxy): 733 value.set_param_desc(self) 734 return value 735 if not hasattr(self, 'ptype') and isNullPointer(value): 736 # deferred evaluation of SimObject; continue to defer if 737 # we're just assigning a null pointer 738 return value 739 if isinstance(value, self.ptype): 740 return value 741 if isNullPointer(value) and issubclass(self.ptype, SimObject): 742 return value 743 return self.ptype(value) 744 745# Vector-valued parameter description. Just like ParamDesc, except 746# that the value is a vector (list) of the specified type instead of a 747# single value. 748 749class VectorParamValue(list): 750 def ini_str(self): 751 return ' '.join([str(v) for v in self]) 752 753 def unproxy(self, base): 754 return [v.unproxy(base) for v in self] 755 756class SimObjVector(VectorParamValue): 757 def print_ini(self): 758 for v in self: 759 v.print_ini() 760 761class VectorParamDesc(ParamDesc): 762 # Convert assigned value to appropriate type. If the RHS is not a 763 # list or tuple, it generates a single-element list. 764 def convert(self, value): 765 if isinstance(value, (list, tuple)): 766 # list: coerce each element into new list 767 tmp_list = [ ParamDesc.convert(self, v) for v in value ] 768 if isSimObjSequence(tmp_list): 769 return SimObjVector(tmp_list) 770 else: 771 return VectorParamValue(tmp_list) 772 else: 773 # singleton: leave it be (could coerce to a single-element 774 # list here, but for some historical reason we don't... 775 return ParamDesc.convert(self, value) 776 777 778class ParamFactory(object): 779 def __init__(self, param_desc_class, ptype_str = None): 780 self.param_desc_class = param_desc_class 781 self.ptype_str = ptype_str 782 783 def __getattr__(self, attr): 784 if self.ptype_str: 785 attr = self.ptype_str + '.' + attr 786 return ParamFactory(self.param_desc_class, attr) 787 788 # E.g., Param.Int(5, "number of widgets") 789 def __call__(self, *args, **kwargs): 790 caller_frame = inspect.stack()[1][0] 791 ptype = None 792 try: 793 ptype = eval(self.ptype_str, 794 caller_frame.f_globals, caller_frame.f_locals) 795 if not isinstance(ptype, type): 796 raise TypeError, \ 797 "Param qualifier is not a type: %s" % ptype 798 except NameError: 799 # if name isn't defined yet, assume it's a SimObject, and 800 # try to resolve it later 801 pass 802 return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs) 803 804Param = ParamFactory(ParamDesc) 805VectorParam = ParamFactory(VectorParamDesc) 806 807##################################################################### 808# 809# Parameter Types 810# 811# Though native Python types could be used to specify parameter types 812# (the 'ptype' field of the Param and VectorParam classes), it's more 813# flexible to define our own set of types. This gives us more control 814# over how Python expressions are converted to values (via the 815# __init__() constructor) and how these values are printed out (via 816# the __str__() conversion method). Eventually we'll need these types 817# to correspond to distinct C++ types as well. 818# 819##################################################################### 820 821class Range(ParamValue): 822 type = int # default; can be overridden in subclasses 823 def __init__(self, *args, **kwargs): 824 825 def handle_kwargs(self, kwargs): 826 if 'end' in kwargs: 827 self.second = self.type(kwargs.pop('end')) 828 elif 'size' in kwargs: 829 self.second = self.first + self.type(kwargs.pop('size')) - 1 830 else: 831 raise TypeError, "Either end or size must be specified" 832 833 if len(args) == 0: 834 self.first = self.type(kwargs.pop('start')) 835 handle_kwargs(self, kwargs) 836 837 elif len(args) == 1: 838 if kwargs: 839 self.first = self.type(args[0]) 840 handle_kwargs(self, kwargs) 841 elif isinstance(args[0], Range): 842 self.first = self.type(args[0].first) 843 self.second = self.type(args[0].second) 844 else: 845 self.first = self.type(0) 846 self.second = self.type(args[0]) - 1 847 848 elif len(args) == 2: 849 self.first = self.type(args[0]) 850 self.second = self.type(args[1]) 851 else: 852 raise TypeError, "Too many arguments specified" 853 854 if kwargs: 855 raise TypeError, "too many keywords: %s" % kwargs.keys() 856 857 def __str__(self): 858 return '%s:%s' % (self.first, self.second) 859 860# Metaclass for bounds-checked integer parameters. See CheckedInt. 861class CheckedIntType(type): 862 def __init__(cls, name, bases, dict): 863 super(CheckedIntType, cls).__init__(name, bases, dict) 864 865 # CheckedInt is an abstract base class, so we actually don't 866 # want to do any processing on it... the rest of this code is 867 # just for classes that derive from CheckedInt. 868 if name == 'CheckedInt': 869 return 870 871 if not (hasattr(cls, 'min') and hasattr(cls, 'max')): 872 if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): 873 panic("CheckedInt subclass %s must define either\n" \ 874 " 'min' and 'max' or 'size' and 'unsigned'\n" \ 875 % name); 876 if cls.unsigned: 877 cls.min = 0 878 cls.max = 2 ** cls.size - 1 879 else: 880 cls.min = -(2 ** (cls.size - 1)) 881 cls.max = (2 ** (cls.size - 1)) - 1 882 883# Abstract superclass for bounds-checked integer parameters. This 884# class is subclassed to generate parameter classes with specific 885# bounds. Initialization of the min and max bounds is done in the 886# metaclass CheckedIntType.__init__. 887class CheckedInt(long,ParamValue): 888 __metaclass__ = CheckedIntType 889 890 def __new__(cls, value): 891 if isinstance(value, str): 892 value = toInteger(value) 893 894 self = long.__new__(cls, value) 895 896 if not cls.min <= self <= cls.max: 897 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ 898 (cls.min, self, cls.max) 899 return self 900 901class Int(CheckedInt): size = 32; unsigned = False 902class Unsigned(CheckedInt): size = 32; unsigned = True 903 904class Int8(CheckedInt): size = 8; unsigned = False 905class UInt8(CheckedInt): size = 8; unsigned = True 906class Int16(CheckedInt): size = 16; unsigned = False 907class UInt16(CheckedInt): size = 16; unsigned = True 908class Int32(CheckedInt): size = 32; unsigned = False 909class UInt32(CheckedInt): size = 32; unsigned = True 910class Int64(CheckedInt): size = 64; unsigned = False 911class UInt64(CheckedInt): size = 64; unsigned = True 912 913class Counter(CheckedInt): size = 64; unsigned = True 914class Tick(CheckedInt): size = 64; unsigned = True 915class TcpPort(CheckedInt): size = 16; unsigned = True 916class UdpPort(CheckedInt): size = 16; unsigned = True 917 918class Percent(CheckedInt): min = 0; max = 100 919 920class MemorySize(CheckedInt): 921 size = 64 922 unsigned = True 923 def __new__(cls, value): 924 return super(MemorySize, cls).__new__(cls, toMemorySize(value)) 925 926 927class Addr(CheckedInt): 928 size = 64 929 unsigned = True 930 def __new__(cls, value): 931 try: 932 value = long(toMemorySize(value)) 933 except TypeError: 934 value = long(value) 935 return super(Addr, cls).__new__(cls, value) 936 937class AddrRange(Range): 938 type = Addr 939 940# String-valued parameter. Just mixin the ParamValue class 941# with the built-in str class. 942class String(ParamValue,str): 943 pass 944 945# Boolean parameter type. Python doesn't let you subclass bool, since 946# it doesn't want to let you create multiple instances of True and 947# False. Thus this is a little more complicated than String. 948class Bool(ParamValue): 949 def __init__(self, value): 950 try: 951 self.value = toBool(value) 952 except TypeError: 953 self.value = bool(value) 954 955 def __str__(self): 956 return str(self.value) 957 958 def ini_str(self): 959 if self.value: 960 return 'true' 961 return 'false' 962 963def IncEthernetAddr(addr, val = 1): 964 bytes = map(lambda x: int(x, 16), addr.split(':')) 965 bytes[5] += val 966 for i in (5, 4, 3, 2, 1): 967 val,rem = divmod(bytes[i], 256) 968 bytes[i] = rem 969 if val == 0: 970 break 971 bytes[i - 1] += val 972 assert(bytes[0] <= 255) 973 return ':'.join(map(lambda x: '%02x' % x, bytes)) 974 975class NextEthernetAddr(object): 976 __metaclass__ = Singleton 977 addr = "00:90:00:00:00:01" 978 979 def __init__(self, inc = 1): 980 self.value = self.addr 981 self.addr = IncEthernetAddr(self.addr, inc) 982 983class EthernetAddr(ParamValue): 984 def __init__(self, value): 985 if value == NextEthernetAddr: 986 self.value = value 987 return 988 989 if not isinstance(value, str): 990 raise TypeError, "expected an ethernet address and didn't get one" 991 992 bytes = value.split(':') 993 if len(bytes) != 6: 994 raise TypeError, 'invalid ethernet address %s' % value 995 996 for byte in bytes: 997 if not 0 <= int(byte) <= 256: 998 raise TypeError, 'invalid ethernet address %s' % value 999 1000 self.value = value 1001 1002 def __str__(self): 1003 if self.value == NextEthernetAddr: 1004 self.value = self.value().value 1005 return self.value 1006 1007# Special class for NULL pointers. Note the special check in 1008# make_param_value() above that lets these be assigned where a 1009# SimObject is required. 1010# only one copy of a particular node 1011class NullSimObject(object): 1012 __metaclass__ = Singleton 1013 1014 def __call__(cls): 1015 return cls 1016 1017 def _instantiate(self, parent = None, path = ''): 1018 pass 1019 1020 def ini_str(self): 1021 return 'Null' 1022 1023# The only instance you'll ever need... 1024Null = NULL = NullSimObject() 1025 1026# Enumerated types are a little more complex. The user specifies the 1027# type as Enum(foo) where foo is either a list or dictionary of 1028# alternatives (typically strings, but not necessarily so). (In the 1029# long run, the integer value of the parameter will be the list index 1030# or the corresponding dictionary value. For now, since we only check 1031# that the alternative is valid and then spit it into a .ini file, 1032# there's not much point in using the dictionary.) 1033 1034# What Enum() must do is generate a new type encapsulating the 1035# provided list/dictionary so that specific values of the parameter 1036# can be instances of that type. We define two hidden internal 1037# classes (_ListEnum and _DictEnum) to serve as base classes, then 1038# derive the new type from the appropriate base class on the fly. 1039 1040 1041# Metaclass for Enum types 1042class MetaEnum(type): 1043 def __init__(cls, name, bases, init_dict): 1044 if init_dict.has_key('map'): 1045 if not isinstance(cls.map, dict): 1046 raise TypeError, "Enum-derived class attribute 'map' " \ 1047 "must be of type dict" 1048 # build list of value strings from map 1049 cls.vals = cls.map.keys() 1050 cls.vals.sort() 1051 elif init_dict.has_key('vals'): 1052 if not isinstance(cls.vals, list): 1053 raise TypeError, "Enum-derived class attribute 'vals' " \ 1054 "must be of type list" 1055 # build string->value map from vals sequence 1056 cls.map = {} 1057 for idx,val in enumerate(cls.vals): 1058 cls.map[val] = idx 1059 else: 1060 raise TypeError, "Enum-derived class must define "\ 1061 "attribute 'map' or 'vals'" 1062 1063 super(MetaEnum, cls).__init__(name, bases, init_dict) 1064 1065 def cpp_declare(cls): 1066 s = 'enum %s {\n ' % cls.__name__ 1067 s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) 1068 s += '\n};\n' 1069 return s 1070 1071# Base class for enum types. 1072class Enum(ParamValue): 1073 __metaclass__ = MetaEnum 1074 vals = [] 1075 1076 def __init__(self, value): 1077 if value not in self.map: 1078 raise TypeError, "Enum param got bad value '%s' (not in %s)" \ 1079 % (value, self.vals) 1080 self.value = value 1081 1082 def __str__(self): 1083 return self.value 1084 1085ticks_per_sec = None 1086 1087# how big does a rounding error need to be before we warn about it? 1088frequency_tolerance = 0.001 # 0.1% 1089 1090# convert a floting-point # of ticks to integer, and warn if rounding 1091# discards too much precision 1092def tick_check(float_ticks): 1093 if float_ticks == 0: 1094 return 0 1095 int_ticks = int(round(float_ticks)) 1096 err = (float_ticks - int_ticks) / float_ticks 1097 if err > frequency_tolerance: 1098 print >> sys.stderr, "Warning: rounding error > tolerance" 1099 return int_ticks 1100 1101# superclass for "numeric" parameter values, to emulate math 1102# operations in a type-safe way. e.g., a Latency times an int returns 1103# a new Latency object. 1104class NumericParamValue(ParamValue): 1105 def __mul__(self, other): 1106 newobj = self.__class__(self) 1107 newobj.value *= other 1108 return newobj 1109 1110 __rmul__ = __mul__ 1111 1112class Latency(NumericParamValue): 1113 def __init__(self, value): 1114 if isinstance(value, Latency): 1115 self.value = value.value 1116 elif isinstance(value, Frequency): 1117 self.value = 1 / value.value 1118 elif isinstance(value, str): 1119 try: 1120 self.value = toLatency(value) 1121 except ValueError: 1122 try: 1123 freq = toFrequency(value) 1124 except ValueError: 1125 raise ValueError, "Latency value '%s' is neither " \ 1126 "frequency nor period" % value 1127 self.value = 1 / freq 1128 elif value == 0: 1129 # the one unitless value that's OK... 1130 self.value = value 1131 else: 1132 raise ValueError, "Invalid Latency value '%s'" % value 1133 1134 def __getattr__(self, attr): 1135 if attr in ('latency', 'period'): 1136 return self 1137 if attr == 'frequency': 1138 return Frequency(self) 1139 raise AttributeError, "Latency object has no attribute '%s'" % attr 1140 1141 def __str__(self): 1142 return str(self.value) 1143 1144 # convert latency to ticks 1145 def ini_str(self): 1146 return str(tick_check(self.value * ticks_per_sec)) 1147 1148class Frequency(NumericParamValue): 1149 def __init__(self, value): 1150 if isinstance(value, Frequency): 1151 self.value = value.value 1152 elif isinstance(value, Latency): 1153 self.value = 1 / value.value 1154 elif isinstance(value, str): 1155 try: 1156 self.value = toFrequency(value) 1157 except ValueError: 1158 try: 1159 freq = toLatency(value) 1160 except ValueError: 1161 raise ValueError, "Frequency value '%s' is neither " \ 1162 "frequency nor period" % value 1163 self.value = 1 / freq 1164 else: 1165 raise ValueError, "Invalid Frequency value '%s'" % value 1166 1167 def __getattr__(self, attr): 1168 if attr == 'frequency': 1169 return self 1170 if attr in ('latency', 'period'): 1171 return Latency(self) 1172 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1173 1174 def __str__(self): 1175 return str(self.value) 1176 1177 def __float__(self): 1178 return float(self.value) 1179 1180 # convert frequency to ticks per period 1181 def ini_str(self): 1182 return self.period.ini_str() 1183 1184# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz) 1185class RootFrequency(Frequency): 1186 def ini_str(self): 1187 return str(tick_check(self.value)) 1188 1189class NetworkBandwidth(float,ParamValue): 1190 def __new__(cls, value): 1191 val = toNetworkBandwidth(value) / 8.0 1192 return super(cls, NetworkBandwidth).__new__(cls, val) 1193 1194 def __str__(self): 1195 return str(self.val) 1196 1197 def ini_str(self): 1198 return '%f' % (ticks_per_sec / self.val) 1199 1200class MemoryBandwidth(float,ParamValue): 1201 def __new__(self, value): 1202 val = toMemoryBandwidth(value) 1203 return super(cls, MemoryBandwidth).__new__(cls, val) 1204 1205 def __str__(self): 1206 return str(self.val) 1207 1208 def ini_str(self): 1209 return '%f' % (ticks_per_sec / self.val) 1210 1211# 1212# "Constants"... handy aliases for various values. 1213# 1214 1215# Some memory range specifications use this as a default upper bound. 1216MaxAddr = Addr.max 1217MaxTick = Tick.max 1218AllMemory = AddrRange(0, MaxAddr) 1219 1220##################################################################### 1221 1222# The final hook to generate .ini files. Called from configuration 1223# script once config is built. 1224def instantiate(root): 1225 global ticks_per_sec 1226 ticks_per_sec = float(root.frequency) 1227 root.print_ini() 1228 noDot = True # temporary until we fix dot 1229 if not noDot: 1230 dot = pydot.Dot() 1231 instance.outputDot(dot) 1232 dot.orientation = "portrait" 1233 dot.size = "8.5,11" 1234 dot.ranksep="equally" 1235 dot.rank="samerank" 1236 dot.write("config.dot") 1237 dot.write_ps("config.ps") 1238 1239# __all__ defines the list of symbols that get exported when 1240# 'from config import *' is invoked. Try to keep this reasonably 1241# short to avoid polluting other namespaces. 1242__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam', 1243 'Parent', 'Self', 1244 'Enum', 'Bool', 'String', 1245 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', 1246 'Int32', 'UInt32', 'Int64', 'UInt64', 1247 'Counter', 'Addr', 'Tick', 'Percent', 1248 'TcpPort', 'UdpPort', 'EthernetAddr', 1249 'MemorySize', 'Latency', 'Frequency', 'RootFrequency', 1250 'NetworkBandwidth', 'MemoryBandwidth', 1251 'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory', 1252 'Null', 'NULL', 1253 'NextEthernetAddr', 'instantiate'] 1254 1255