read_config.py revision 10458:64809024b924
1# Copyright (c) 2014 ARM Limited 2# All rights reserved. 3# 4# The license below extends only to copyright in the software and shall 5# not be construed as granting a license to any other intellectual 6# property including but not limited to intellectual property relating 7# to a hardware implementation of the functionality of the software 8# licensed hereunder. You may use the software subject to the license 9# terms below provided that you ensure that this notice is replicated 10# unmodified and in its entirety in all distributions of the software, 11# modified or unmodified, in source code or in binary form. 12# 13# Redistribution and use in source and binary forms, with or without 14# modification, are permitted provided that the following conditions are 15# met: redistributions of source code must retain the above copyright 16# notice, this list of conditions and the following disclaimer; 17# redistributions in binary form must reproduce the above copyright 18# notice, this list of conditions and the following disclaimer in the 19# documentation and/or other materials provided with the distribution; 20# neither the name of the copyright holders nor the names of its 21# contributors may be used to endorse or promote products derived from 22# this software without specific prior written permission. 23# 24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35# 36# Author: Andrew Bardsley 37 38# This script allows .ini and .json system config file generated from a 39# previous gem5 run to be read in and instantiated. 40# 41# This may be useful as a way of allowing variant run scripts (say, 42# with more complicated than usual checkpointing/stats dumping/ 43# simulation control) to read pre-described systems from config scripts 44# with better system-description capabilities. Splitting scripts 45# between system construction and run control may allow better 46# debugging. 47 48import argparse 49import ConfigParser 50import inspect 51import json 52import re 53import sys 54 55import m5 56import m5.ticks as ticks 57 58sim_object_classes_by_name = { 59 cls.__name__: cls for cls in m5.objects.__dict__.itervalues() 60 if inspect.isclass(cls) and issubclass(cls, m5.objects.SimObject) } 61 62# Add some parsing functions to Param classes to handle reading in .ini 63# file elements. This could be moved into src/python/m5/params.py if 64# reading .ini files from Python proves to be useful 65 66def no_parser(cls, flags, param): 67 raise Exception('Can\'t parse string: %s for parameter' 68 ' class: %s' % (str(param), cls.__name__)) 69 70def simple_parser(suffix='', cast=lambda i: i): 71 def body(cls, flags, param): 72 return cls(cast(param + suffix)) 73 return body 74 75# def tick_parser(cast=m5.objects.Latency): # lambda i: i): 76def tick_parser(cast=lambda i: i): 77 def body(cls, flags, param): 78 old_param = param 79 ret = cls(cast(str(param) + 't')) 80 return ret 81 return body 82 83def addr_range_parser(cls, flags, param): 84 sys.stdout.flush() 85 low, high = param.split(':') 86 return m5.objects.AddrRange(long(low), long(high)) 87 88def memory_bandwidth_parser(cls, flags, param): 89 # The string will be in tick/byte 90 # Convert to byte/tick 91 value = 1.0 / float(param) 92 # Convert to byte/s 93 value = ticks.fromSeconds(value) 94 return cls('%fB/s' % value) 95 96# These parameters have trickier parsing from .ini files than might be 97# expected 98param_parsers = { 99 'Bool': simple_parser(), 100 'ParamValue': no_parser, 101 'NumericParamValue': simple_parser(cast=long), 102 'TickParamValue': tick_parser(), 103 'Frequency': tick_parser(cast=m5.objects.Latency), 104 'Voltage': simple_parser(suffix='V'), 105 'Enum': simple_parser(), 106 'MemorySize': simple_parser(suffix='B'), 107 'MemorySize32': simple_parser(suffix='B'), 108 'AddrRange': addr_range_parser, 109 'String': simple_parser(), 110 'MemoryBandwidth': memory_bandwidth_parser, 111 'Time': simple_parser() 112 } 113 114for name, parser in param_parsers.iteritems(): 115 setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser)) 116 117class PortConnection(object): 118 """This class is similar to m5.params.PortRef but with just enough 119 information for ConfigManager""" 120 121 def __init__(self, object_name, port_name, index): 122 self.object_name = object_name 123 self.port_name = port_name 124 self.index = index 125 126 @classmethod 127 def from_string(cls, str): 128 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', str) 129 object_name, port_name, whole_index, index = m.groups() 130 if index is not None: 131 index = int(index) 132 else: 133 index = 0 134 135 return PortConnection(object_name, port_name, index) 136 137 def __str__(self): 138 return '%s.%s[%d]' % (self.object_name, self.port_name, self.index) 139 140 def __cmp__(self, right): 141 return cmp((self.object_name, self.port_name, self.index), 142 (right.object_name, right.port_name, right.index)) 143 144def to_list(v): 145 """Convert any non list to a singleton list""" 146 if isinstance(v, list): 147 return v 148 else: 149 return [v] 150 151class ConfigManager(object): 152 """Manager for parsing a Root configuration from a config file""" 153 def __init__(self, config): 154 self.config = config 155 self.objects_by_name = {} 156 self.flags = config.get_flags() 157 158 def find_object(self, object_name): 159 """Find and configure (with just non-SimObject parameters) 160 a single object""" 161 162 if object_name == 'Null': 163 return NULL 164 165 if object_name in self.objects_by_name: 166 return self.objects_by_name[object_name] 167 168 object_type = self.config.get_param(object_name, 'type') 169 170 if object_type not in sim_object_classes_by_name: 171 raise Exception('No SimObject type %s is available to' 172 ' build: %s' % (object_type, object_name)) 173 174 object_class = sim_object_classes_by_name[object_type] 175 176 parsed_params = {} 177 178 for param_name, param in object_class._params.iteritems(): 179 if issubclass(param.ptype, m5.params.ParamValue): 180 if isinstance(param, m5.params.VectorParamDesc): 181 param_values = self.config.get_param_vector(object_name, 182 param_name) 183 184 param_value = [ param.ptype.parse_ini(self.flags, value) 185 for value in param_values ] 186 else: 187 param_value = param.ptype.parse_ini( 188 self.flags, self.config.get_param(object_name, 189 param_name)) 190 191 parsed_params[param_name] = param_value 192 193 obj = object_class(**parsed_params) 194 self.objects_by_name[object_name] = obj 195 196 return obj 197 198 def fill_in_simobj_parameters(self, object_name, obj): 199 """Fill in all references to other SimObjects in an objects 200 parameters. This relies on all referenced objects having been 201 created""" 202 203 if object_name == 'Null': 204 return NULL 205 206 for param_name, param in obj.__class__._params.iteritems(): 207 if issubclass(param.ptype, m5.objects.SimObject): 208 if isinstance(param, m5.params.VectorParamDesc): 209 param_values = self.config.get_param_vector(object_name, 210 param_name) 211 212 setattr(obj, param_name, [ self.objects_by_name[name] 213 for name in param_values ]) 214 else: 215 param_value = self.config.get_param(object_name, 216 param_name) 217 218 if param_value != 'Null': 219 setattr(obj, param_name, self.objects_by_name[ 220 param_value]) 221 222 return obj 223 224 def fill_in_children(self, object_name, obj): 225 """Fill in the children of this object. This relies on all the 226 referenced objects having been created""" 227 228 children = self.config.get_object_children(object_name) 229 230 for child_name, child_paths in children: 231 param = obj.__class__._params.get(child_name, None) 232 233 if isinstance(child_paths, list): 234 child_list = [ self.objects_by_name[path] 235 for path in child_paths ] 236 else: 237 child_list = self.objects_by_name[child_paths] 238 239 obj.add_child(child_name, child_list) 240 241 for path in to_list(child_paths): 242 self.fill_in_children(path, self.objects_by_name[path]) 243 244 return obj 245 246 def parse_port_name(self, port): 247 """Parse the name of a port""" 248 249 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', port) 250 peer, peer_port, whole_index, index = m.groups() 251 if index is not None: 252 index = int(index) 253 else: 254 index = 0 255 256 return (peer, self.objects_by_name[peer], peer_port, index) 257 258 def gather_port_connections(self, object_name, obj): 259 """Gather all the port-to-port connections from the named object. 260 Returns a list of (PortConnection, PortConnection) with unordered 261 (wrt. master/slave) connection information""" 262 263 if object_name == 'Null': 264 return NULL 265 266 parsed_ports = [] 267 for port_name, port in obj.__class__._ports.iteritems(): 268 # Assume that unnamed ports are unconnected 269 peers = self.config.get_port_peers(object_name, port_name) 270 271 for index, peer in zip(xrange(0, len(peers)), peers): 272 parsed_ports.append(( 273 PortConnection(object_name, port.name, index), 274 PortConnection.from_string(peer))) 275 276 return parsed_ports 277 278 def bind_ports(self, connections): 279 """Bind all ports from the given connection list. Note that the 280 connection list *must* list all connections with both (slave,master) 281 and (master,slave) orderings""" 282 283 # Markup a dict of how many connections are made to each port. 284 # This will be used to check that the next-to-be-made connection 285 # has a suitable port index 286 port_bind_indices = {} 287 for from_port, to_port in connections: 288 port_bind_indices[ 289 (from_port.object_name, from_port.port_name)] = 0 290 291 def port_has_correct_index(port): 292 return port_bind_indices[ 293 (port.object_name, port.port_name)] == port.index 294 295 def increment_port_index(port): 296 port_bind_indices[ 297 (port.object_name, port.port_name)] += 1 298 299 # Step through the sorted connections. Exactly one of 300 # each (slave,master) and (master,slave) pairs will be 301 # bindable because the connections are sorted. 302 # For example: port_bind_indices 303 # left right left right 304 # a.b[0] -> d.f[1] 0 0 X 305 # a.b[1] -> e.g 0 0 BIND! 306 # e.g -> a.b[1] 1 X 0 307 # d.f[0] -> f.h 0 0 BIND! 308 # d.f[1] -> a.b[0] 1 0 BIND! 309 connections_to_make = [] 310 for connection in sorted(connections): 311 from_port, to_port = connection 312 313 if (port_has_correct_index(from_port) and 314 port_has_correct_index(to_port)): 315 316 connections_to_make.append((from_port, to_port)) 317 318 increment_port_index(from_port) 319 increment_port_index(to_port) 320 321 # Exactly half of the connections (ie. all of them, one per 322 # direction) must now have been made 323 if (len(connections_to_make) * 2) != len(connections): 324 raise Exception('Port bindings can\'t be ordered') 325 326 # Actually do the binding 327 for from_port, to_port in connections_to_make: 328 from_object = self.objects_by_name[from_port.object_name] 329 to_object = self.objects_by_name[to_port.object_name] 330 331 setattr(from_object, from_port.port_name, 332 getattr(to_object, to_port.port_name)) 333 334 def find_all_objects(self): 335 """Find and build all SimObjects from the config file and connect 336 their ports together as described. Does not instantiate system""" 337 338 # Build SimObjects for all sections of the config file 339 # populating not-SimObject-valued parameters 340 for object_name in self.config.get_all_object_names(): 341 self.find_object(object_name) 342 343 # Add children to objects in the hierarchy from root 344 self.fill_in_children('root', self.find_object('root')) 345 346 # Now fill in SimObject-valued parameters in the knowledge that 347 # this won't be interpreted as becoming the parent of objects 348 # which are already in the root hierarchy 349 for name, obj in self.objects_by_name.iteritems(): 350 self.fill_in_simobj_parameters(name, obj) 351 352 # Gather a list of all port-to-port connections 353 connections = [] 354 for name, obj in self.objects_by_name.iteritems(): 355 connections += self.gather_port_connections(name, obj) 356 357 # Find an acceptable order to bind those port connections and 358 # bind them 359 self.bind_ports(connections) 360 361class ConfigFile(object): 362 def get_flags(self): 363 return set() 364 365 def load(self, config_file): 366 """Load the named config file""" 367 pass 368 369 def get_all_object_names(self): 370 """Get a list of all the SimObject paths in the configuration""" 371 pass 372 373 def get_param(self, object_name, param_name): 374 """Get a single param or SimObject reference from the configuration 375 as a string""" 376 pass 377 378 def get_param_vector(self, object_name, param_name): 379 """Get a vector param or vector of SimObject references from the 380 configuration as a list of strings""" 381 pass 382 383 def get_object_children(self, object_name): 384 """Get a list of (name, paths) for each child of this object. 385 paths is either a single string object path or a list of object 386 paths""" 387 pass 388 389 def get_port_peers(self, object_name, port_name): 390 """Get the list of connected port names (in the string form 391 object.port(\[index\])?) of the port object_name.port_name""" 392 pass 393 394class ConfigIniFile(ConfigFile): 395 def __init__(self): 396 self.parser = ConfigParser.ConfigParser() 397 398 def load(self, config_file): 399 self.parser.read(config_file) 400 401 def get_all_object_names(self): 402 return self.parser.sections() 403 404 def get_param(self, object_name, param_name): 405 return self.parser.get(object_name, param_name) 406 407 def get_param_vector(self, object_name, param_name): 408 return self.parser.get(object_name, param_name).split() 409 410 def get_object_children(self, object_name): 411 if self.parser.has_option(object_name, 'children'): 412 children = self.parser.get(object_name, 'children') 413 child_names = children.split() 414 else: 415 child_names = [] 416 417 def make_path(child_name): 418 if object_name == 'root': 419 return child_name 420 else: 421 return '%s.%s' % (object_name, child_name) 422 423 return [ (name, make_path(name)) for name in child_names ] 424 425 def get_port_peers(self, object_name, port_name): 426 if self.parser.has_option(object_name, port_name): 427 peer_string = self.parser.get(object_name, port_name) 428 return peer_string.split() 429 else: 430 return [] 431 432class ConfigJsonFile(ConfigFile): 433 def __init__(self): 434 pass 435 436 def is_sim_object(self, node): 437 return isinstance(node, dict) and 'path' in node 438 439 def find_all_objects(self, node): 440 if self.is_sim_object(node): 441 self.object_dicts[node['path']] = node 442 443 if isinstance(node, list): 444 for elem in node: 445 self.find_all_objects(elem) 446 elif isinstance(node, dict): 447 for elem in node.itervalues(): 448 self.find_all_objects(elem) 449 450 def load(self, config_file): 451 root = json.load(open(config_file, 'r')) 452 self.object_dicts = {} 453 self.find_all_objects(root) 454 455 def get_all_object_names(self): 456 return sorted(self.object_dicts.keys()) 457 458 def parse_param_string(self, node): 459 if node is None: 460 return "Null" 461 elif self.is_sim_object(node): 462 return node['path'] 463 else: 464 return str(node) 465 466 def get_param(self, object_name, param_name): 467 obj = self.object_dicts[object_name] 468 469 return self.parse_param_string(obj[param_name]) 470 471 def get_param_vector(self, object_name, param_name): 472 obj = self.object_dicts[object_name] 473 474 return [ self.parse_param_string(p) for p in obj[param_name] ] 475 476 def get_object_children(self, object_name): 477 """It is difficult to tell which elements are children in the 478 JSON file as there is no explicit 'children' node. Take any 479 element which is a full SimObject description or a list of 480 SimObject descriptions. This will not work with a mixed list of 481 references and descriptions but that's a scenario that isn't 482 possible (very likely?) with gem5's binding/naming rules""" 483 obj = self.object_dicts[object_name] 484 485 children = [] 486 for name, node in obj.iteritems(): 487 if self.is_sim_object(node): 488 children.append((name, node['path'])) 489 elif isinstance(node, list) and node != [] and all([ 490 self.is_sim_object(e) for e in node ]): 491 children.append((name, [ e['path'] for e in node ])) 492 493 return children 494 495 def get_port_peers(self, object_name, port_name): 496 """Get the 'peer' element of any node with 'peer' and 'role' 497 elements""" 498 obj = self.object_dicts[object_name] 499 500 peers = [] 501 if port_name in obj and 'peer' in obj[port_name] and \ 502 'role' in obj[port_name]: 503 peers = to_list(obj[port_name]['peer']) 504 505 return peers 506 507parser = argparse.ArgumentParser() 508 509parser.add_argument('config_file', metavar='config-file.ini', 510 help='.ini configuration file to load and run') 511 512args = parser.parse_args(sys.argv[1:]) 513 514if args.config_file.endswith('.ini'): 515 config = ConfigIniFile() 516 config.load(args.config_file) 517else: 518 config = ConfigJsonFile() 519 config.load(args.config_file) 520 521ticks.fixGlobalFrequency() 522 523mgr = ConfigManager(config) 524 525mgr.find_all_objects() 526 527m5.instantiate() 528 529exit_event = m5.simulate() 530print 'Exiting @ tick %i because %s' % ( 531 m5.curTick(), exit_event.getCause()) 532