read_config.py revision 13774
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 48from __future__ import print_function 49from __future__ import absolute_import 50 51import argparse 52import ConfigParser 53import inspect 54import json 55import re 56import sys 57 58import m5 59import m5.ticks as ticks 60 61sim_object_classes_by_name = { 62 cls.__name__: cls for cls in m5.objects.__dict__.itervalues() 63 if inspect.isclass(cls) and issubclass(cls, m5.objects.SimObject) } 64 65# Add some parsing functions to Param classes to handle reading in .ini 66# file elements. This could be moved into src/python/m5/params.py if 67# reading .ini files from Python proves to be useful 68 69def no_parser(cls, flags, param): 70 raise Exception('Can\'t parse string: %s for parameter' 71 ' class: %s' % (str(param), cls.__name__)) 72 73def simple_parser(suffix='', cast=lambda i: i): 74 def body(cls, flags, param): 75 return cls(cast(param + suffix)) 76 return body 77 78# def tick_parser(cast=m5.objects.Latency): # lambda i: i): 79def tick_parser(cast=lambda i: i): 80 def body(cls, flags, param): 81 old_param = param 82 ret = cls(cast(str(param) + 't')) 83 return ret 84 return body 85 86def addr_range_parser(cls, flags, param): 87 sys.stdout.flush() 88 (low, high, intlv_high_bit, xor_high_bit, 89 intlv_bits, intlv_match) = param.split(':') 90 return m5.objects.AddrRange( 91 start=long(low), end=long(high), 92 intlvHighBit=long(intlv_high_bit), xorHighBit=long(xor_high_bit), 93 intlvBits=long(intlv_bits), intlvMatch=long(intlv_match)) 94 95def memory_bandwidth_parser(cls, flags, param): 96 # The string will be in tick/byte 97 # Convert to byte/tick 98 value = 1.0 / float(param) 99 # Convert to byte/s 100 value = ticks.fromSeconds(value) 101 return cls('%fB/s' % value) 102 103# These parameters have trickier parsing from .ini files than might be 104# expected 105param_parsers = { 106 'Bool': simple_parser(), 107 'ParamValue': no_parser, 108 'NumericParamValue': simple_parser(cast=long), 109 'TickParamValue': tick_parser(), 110 'Frequency': tick_parser(cast=m5.objects.Latency), 111 'Current': simple_parser(suffix='A'), 112 'Voltage': simple_parser(suffix='V'), 113 'Enum': simple_parser(), 114 'MemorySize': simple_parser(suffix='B'), 115 'MemorySize32': simple_parser(suffix='B'), 116 'AddrRange': addr_range_parser, 117 'String': simple_parser(), 118 'MemoryBandwidth': memory_bandwidth_parser, 119 'Time': simple_parser(), 120 'EthernetAddr': simple_parser() 121 } 122 123for name, parser in param_parsers.iteritems(): 124 setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser)) 125 126class PortConnection(object): 127 """This class is similar to m5.params.PortRef but with just enough 128 information for ConfigManager""" 129 130 def __init__(self, object_name, port_name, index): 131 self.object_name = object_name 132 self.port_name = port_name 133 self.index = index 134 135 @classmethod 136 def from_string(cls, str): 137 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', str) 138 object_name, port_name, whole_index, index = m.groups() 139 if index is not None: 140 index = int(index) 141 else: 142 index = 0 143 144 return PortConnection(object_name, port_name, index) 145 146 def __str__(self): 147 return '%s.%s[%d]' % (self.object_name, self.port_name, self.index) 148 149 def __cmp__(self, right): 150 return cmp((self.object_name, self.port_name, self.index), 151 (right.object_name, right.port_name, right.index)) 152 153def to_list(v): 154 """Convert any non list to a singleton list""" 155 if isinstance(v, list): 156 return v 157 else: 158 return [v] 159 160class ConfigManager(object): 161 """Manager for parsing a Root configuration from a config file""" 162 def __init__(self, config): 163 self.config = config 164 self.objects_by_name = {} 165 self.flags = config.get_flags() 166 167 def find_object(self, object_name): 168 """Find and configure (with just non-SimObject parameters) 169 a single object""" 170 171 if object_name == 'Null': 172 return NULL 173 174 if object_name in self.objects_by_name: 175 return self.objects_by_name[object_name] 176 177 object_type = self.config.get_param(object_name, 'type') 178 179 if object_type not in sim_object_classes_by_name: 180 raise Exception('No SimObject type %s is available to' 181 ' build: %s' % (object_type, object_name)) 182 183 object_class = sim_object_classes_by_name[object_type] 184 185 parsed_params = {} 186 187 for param_name, param in object_class._params.iteritems(): 188 if issubclass(param.ptype, m5.params.ParamValue): 189 if isinstance(param, m5.params.VectorParamDesc): 190 param_values = self.config.get_param_vector(object_name, 191 param_name) 192 193 param_value = [ param.ptype.parse_ini(self.flags, value) 194 for value in param_values ] 195 else: 196 param_value = param.ptype.parse_ini( 197 self.flags, self.config.get_param(object_name, 198 param_name)) 199 200 parsed_params[param_name] = param_value 201 202 obj = object_class(**parsed_params) 203 self.objects_by_name[object_name] = obj 204 205 return obj 206 207 def fill_in_simobj_parameters(self, object_name, obj): 208 """Fill in all references to other SimObjects in an objects 209 parameters. This relies on all referenced objects having been 210 created""" 211 212 if object_name == 'Null': 213 return NULL 214 215 for param_name, param in obj.__class__._params.iteritems(): 216 if issubclass(param.ptype, m5.objects.SimObject): 217 if isinstance(param, m5.params.VectorParamDesc): 218 param_values = self.config.get_param_vector(object_name, 219 param_name) 220 221 setattr(obj, param_name, 222 [ self.objects_by_name[name] 223 if name != 'Null' else m5.params.NULL 224 for name in param_values ]) 225 else: 226 param_value = self.config.get_param(object_name, 227 param_name) 228 229 if param_value != 'Null': 230 setattr(obj, param_name, self.objects_by_name[ 231 param_value]) 232 233 return obj 234 235 def fill_in_children(self, object_name, obj): 236 """Fill in the children of this object. This relies on all the 237 referenced objects having been created""" 238 239 children = self.config.get_object_children(object_name) 240 241 for child_name, child_paths in children: 242 param = obj.__class__._params.get(child_name, None) 243 if child_name == 'Null': 244 continue 245 246 if isinstance(child_paths, list): 247 child_list = [ self.objects_by_name[path] 248 for path in child_paths ] 249 else: 250 child_list = self.objects_by_name[child_paths] 251 252 obj.add_child(child_name, child_list) 253 254 for path in to_list(child_paths): 255 self.fill_in_children(path, self.objects_by_name[path]) 256 257 return obj 258 259 def parse_port_name(self, port): 260 """Parse the name of a port""" 261 262 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', port) 263 peer, peer_port, whole_index, index = m.groups() 264 if index is not None: 265 index = int(index) 266 else: 267 index = 0 268 269 return (peer, self.objects_by_name[peer], peer_port, index) 270 271 def gather_port_connections(self, object_name, obj): 272 """Gather all the port-to-port connections from the named object. 273 Returns a list of (PortConnection, PortConnection) with unordered 274 (wrt. master/slave) connection information""" 275 276 if object_name == 'Null': 277 return NULL 278 279 parsed_ports = [] 280 for port_name, port in obj.__class__._ports.iteritems(): 281 # Assume that unnamed ports are unconnected 282 peers = self.config.get_port_peers(object_name, port_name) 283 284 for index, peer in zip(range(0, len(peers)), peers): 285 parsed_ports.append(( 286 PortConnection(object_name, port.name, index), 287 PortConnection.from_string(peer))) 288 289 return parsed_ports 290 291 def bind_ports(self, connections): 292 """Bind all ports from the given connection list. Note that the 293 connection list *must* list all connections with both (slave,master) 294 and (master,slave) orderings""" 295 296 # Markup a dict of how many connections are made to each port. 297 # This will be used to check that the next-to-be-made connection 298 # has a suitable port index 299 port_bind_indices = {} 300 for from_port, to_port in connections: 301 port_bind_indices[ 302 (from_port.object_name, from_port.port_name)] = 0 303 304 def port_has_correct_index(port): 305 return port_bind_indices[ 306 (port.object_name, port.port_name)] == port.index 307 308 def increment_port_index(port): 309 port_bind_indices[ 310 (port.object_name, port.port_name)] += 1 311 312 # Step through the sorted connections. Exactly one of 313 # each (slave,master) and (master,slave) pairs will be 314 # bindable because the connections are sorted. 315 # For example: port_bind_indices 316 # left right left right 317 # a.b[0] -> d.f[1] 0 0 X 318 # a.b[1] -> e.g 0 0 BIND! 319 # e.g -> a.b[1] 1 X 0 320 # d.f[0] -> f.h 0 0 BIND! 321 # d.f[1] -> a.b[0] 1 0 BIND! 322 connections_to_make = [] 323 for connection in sorted(connections): 324 from_port, to_port = connection 325 326 if (port_has_correct_index(from_port) and 327 port_has_correct_index(to_port)): 328 329 connections_to_make.append((from_port, to_port)) 330 331 increment_port_index(from_port) 332 increment_port_index(to_port) 333 334 # Exactly half of the connections (ie. all of them, one per 335 # direction) must now have been made 336 if (len(connections_to_make) * 2) != len(connections): 337 raise Exception('Port bindings can\'t be ordered') 338 339 # Actually do the binding 340 for from_port, to_port in connections_to_make: 341 from_object = self.objects_by_name[from_port.object_name] 342 to_object = self.objects_by_name[to_port.object_name] 343 344 setattr(from_object, from_port.port_name, 345 getattr(to_object, to_port.port_name)) 346 347 def find_all_objects(self): 348 """Find and build all SimObjects from the config file and connect 349 their ports together as described. Does not instantiate system""" 350 351 # Build SimObjects for all sections of the config file 352 # populating not-SimObject-valued parameters 353 for object_name in self.config.get_all_object_names(): 354 self.find_object(object_name) 355 356 # Add children to objects in the hierarchy from root 357 self.fill_in_children('root', self.find_object('root')) 358 359 # Now fill in SimObject-valued parameters in the knowledge that 360 # this won't be interpreted as becoming the parent of objects 361 # which are already in the root hierarchy 362 for name, obj in self.objects_by_name.iteritems(): 363 self.fill_in_simobj_parameters(name, obj) 364 365 # Gather a list of all port-to-port connections 366 connections = [] 367 for name, obj in self.objects_by_name.iteritems(): 368 connections += self.gather_port_connections(name, obj) 369 370 # Find an acceptable order to bind those port connections and 371 # bind them 372 self.bind_ports(connections) 373 374class ConfigFile(object): 375 def get_flags(self): 376 return set() 377 378 def load(self, config_file): 379 """Load the named config file""" 380 pass 381 382 def get_all_object_names(self): 383 """Get a list of all the SimObject paths in the configuration""" 384 pass 385 386 def get_param(self, object_name, param_name): 387 """Get a single param or SimObject reference from the configuration 388 as a string""" 389 pass 390 391 def get_param_vector(self, object_name, param_name): 392 """Get a vector param or vector of SimObject references from the 393 configuration as a list of strings""" 394 pass 395 396 def get_object_children(self, object_name): 397 """Get a list of (name, paths) for each child of this object. 398 paths is either a single string object path or a list of object 399 paths""" 400 pass 401 402 def get_port_peers(self, object_name, port_name): 403 """Get the list of connected port names (in the string form 404 object.port(\[index\])?) of the port object_name.port_name""" 405 pass 406 407class ConfigIniFile(ConfigFile): 408 def __init__(self): 409 self.parser = ConfigParser.ConfigParser() 410 411 def load(self, config_file): 412 self.parser.read(config_file) 413 414 def get_all_object_names(self): 415 return self.parser.sections() 416 417 def get_param(self, object_name, param_name): 418 return self.parser.get(object_name, param_name) 419 420 def get_param_vector(self, object_name, param_name): 421 return self.parser.get(object_name, param_name).split() 422 423 def get_object_children(self, object_name): 424 if self.parser.has_option(object_name, 'children'): 425 children = self.parser.get(object_name, 'children') 426 child_names = children.split() 427 else: 428 child_names = [] 429 430 def make_path(child_name): 431 if object_name == 'root': 432 return child_name 433 else: 434 return '%s.%s' % (object_name, child_name) 435 436 return [ (name, make_path(name)) for name in child_names ] 437 438 def get_port_peers(self, object_name, port_name): 439 if self.parser.has_option(object_name, port_name): 440 peer_string = self.parser.get(object_name, port_name) 441 return peer_string.split() 442 else: 443 return [] 444 445class ConfigJsonFile(ConfigFile): 446 def __init__(self): 447 pass 448 449 def is_sim_object(self, node): 450 return isinstance(node, dict) and 'path' in node 451 452 def find_all_objects(self, node): 453 if self.is_sim_object(node): 454 self.object_dicts[node['path']] = node 455 456 if isinstance(node, list): 457 for elem in node: 458 self.find_all_objects(elem) 459 elif isinstance(node, dict): 460 for elem in node.itervalues(): 461 self.find_all_objects(elem) 462 463 def load(self, config_file): 464 root = json.load(open(config_file, 'r')) 465 self.object_dicts = {} 466 self.find_all_objects(root) 467 468 def get_all_object_names(self): 469 return sorted(self.object_dicts.keys()) 470 471 def parse_param_string(self, node): 472 if node is None: 473 return "Null" 474 elif self.is_sim_object(node): 475 return node['path'] 476 else: 477 return str(node) 478 479 def get_param(self, object_name, param_name): 480 obj = self.object_dicts[object_name] 481 482 return self.parse_param_string(obj[param_name]) 483 484 def get_param_vector(self, object_name, param_name): 485 obj = self.object_dicts[object_name] 486 487 return [ self.parse_param_string(p) for p in obj[param_name] ] 488 489 def get_object_children(self, object_name): 490 """It is difficult to tell which elements are children in the 491 JSON file as there is no explicit 'children' node. Take any 492 element which is a full SimObject description or a list of 493 SimObject descriptions. This will not work with a mixed list of 494 references and descriptions but that's a scenario that isn't 495 possible (very likely?) with gem5's binding/naming rules""" 496 obj = self.object_dicts[object_name] 497 498 children = [] 499 for name, node in obj.iteritems(): 500 if self.is_sim_object(node): 501 children.append((name, node['path'])) 502 elif isinstance(node, list) and node != [] and all([ 503 self.is_sim_object(e) for e in node ]): 504 children.append((name, [ e['path'] for e in node ])) 505 506 return children 507 508 def get_port_peers(self, object_name, port_name): 509 """Get the 'peer' element of any node with 'peer' and 'role' 510 elements""" 511 obj = self.object_dicts[object_name] 512 513 peers = [] 514 if port_name in obj and 'peer' in obj[port_name] and \ 515 'role' in obj[port_name]: 516 peers = to_list(obj[port_name]['peer']) 517 518 return peers 519 520parser = argparse.ArgumentParser() 521 522parser.add_argument('config_file', metavar='config-file.ini', 523 help='.ini configuration file to load and run') 524parser.add_argument('--checkpoint-dir', type=str, default=None, 525 help='A checkpoint to directory to restore when starting ' 526 'the simulation') 527 528args = parser.parse_args(sys.argv[1:]) 529 530if args.config_file.endswith('.ini'): 531 config = ConfigIniFile() 532 config.load(args.config_file) 533else: 534 config = ConfigJsonFile() 535 config.load(args.config_file) 536 537ticks.fixGlobalFrequency() 538 539mgr = ConfigManager(config) 540 541mgr.find_all_objects() 542 543m5.instantiate(args.checkpoint_dir) 544 545exit_event = m5.simulate() 546print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause())) 547