# Copyright (c) 2014 ARM Limited # All rights reserved. # # The license below extends only to copyright in the software and shall # not be construed as granting a license to any other intellectual # property including but not limited to intellectual property relating # to a hardware implementation of the functionality of the software # licensed hereunder. You may use the software subject to the license # terms below provided that you ensure that this notice is replicated # unmodified and in its entirety in all distributions of the software, # modified or unmodified, in source code or in binary form. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Author: Andrew Bardsley # This script allows .ini and .json system config file generated from a # previous gem5 run to be read in and instantiated. # # This may be useful as a way of allowing variant run scripts (say, # with more complicated than usual checkpointing/stats dumping/ # simulation control) to read pre-described systems from config scripts # with better system-description capabilities. Splitting scripts # between system construction and run control may allow better # debugging. from __future__ import print_function import argparse import ConfigParser import inspect import json import re import sys import m5 import m5.ticks as ticks sim_object_classes_by_name = { cls.__name__: cls for cls in m5.objects.__dict__.itervalues() if inspect.isclass(cls) and issubclass(cls, m5.objects.SimObject) } # Add some parsing functions to Param classes to handle reading in .ini # file elements. This could be moved into src/python/m5/params.py if # reading .ini files from Python proves to be useful def no_parser(cls, flags, param): raise Exception('Can\'t parse string: %s for parameter' ' class: %s' % (str(param), cls.__name__)) def simple_parser(suffix='', cast=lambda i: i): def body(cls, flags, param): return cls(cast(param + suffix)) return body # def tick_parser(cast=m5.objects.Latency): # lambda i: i): def tick_parser(cast=lambda i: i): def body(cls, flags, param): old_param = param ret = cls(cast(str(param) + 't')) return ret return body def addr_range_parser(cls, flags, param): sys.stdout.flush() (low, high, intlv_high_bit, xor_high_bit, intlv_bits, intlv_match) = param.split(':') return m5.objects.AddrRange( start=long(low), end=long(high), intlvHighBit=long(intlv_high_bit), xorHighBit=long(xor_high_bit), intlvBits=long(intlv_bits), intlvMatch=long(intlv_match)) def memory_bandwidth_parser(cls, flags, param): # The string will be in tick/byte # Convert to byte/tick value = 1.0 / float(param) # Convert to byte/s value = ticks.fromSeconds(value) return cls('%fB/s' % value) # These parameters have trickier parsing from .ini files than might be # expected param_parsers = { 'Bool': simple_parser(), 'ParamValue': no_parser, 'NumericParamValue': simple_parser(cast=long), 'TickParamValue': tick_parser(), 'Frequency': tick_parser(cast=m5.objects.Latency), 'Current': simple_parser(suffix='A'), 'Voltage': simple_parser(suffix='V'), 'Enum': simple_parser(), 'MemorySize': simple_parser(suffix='B'), 'MemorySize32': simple_parser(suffix='B'), 'AddrRange': addr_range_parser, 'String': simple_parser(), 'MemoryBandwidth': memory_bandwidth_parser, 'Time': simple_parser(), 'EthernetAddr': simple_parser() } for name, parser in param_parsers.iteritems(): setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser)) class PortConnection(object): """This class is similar to m5.params.PortRef but with just enough information for ConfigManager""" def __init__(self, object_name, port_name, index): self.object_name = object_name self.port_name = port_name self.index = index @classmethod def from_string(cls, str): m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', str) object_name, port_name, whole_index, index = m.groups() if index is not None: index = int(index) else: index = 0 return PortConnection(object_name, port_name, index) def __str__(self): return '%s.%s[%d]' % (self.object_name, self.port_name, self.index) def __cmp__(self, right): return cmp((self.object_name, self.port_name, self.index), (right.object_name, right.port_name, right.index)) def to_list(v): """Convert any non list to a singleton list""" if isinstance(v, list): return v else: return [v] class ConfigManager(object): """Manager for parsing a Root configuration from a config file""" def __init__(self, config): self.config = config self.objects_by_name = {} self.flags = config.get_flags() def find_object(self, object_name): """Find and configure (with just non-SimObject parameters) a single object""" if object_name == 'Null': return NULL if object_name in self.objects_by_name: return self.objects_by_name[object_name] object_type = self.config.get_param(object_name, 'type') if object_type not in sim_object_classes_by_name: raise Exception('No SimObject type %s is available to' ' build: %s' % (object_type, object_name)) object_class = sim_object_classes_by_name[object_type] parsed_params = {} for param_name, param in object_class._params.iteritems(): if issubclass(param.ptype, m5.params.ParamValue): if isinstance(param, m5.params.VectorParamDesc): param_values = self.config.get_param_vector(object_name, param_name) param_value = [ param.ptype.parse_ini(self.flags, value) for value in param_values ] else: param_value = param.ptype.parse_ini( self.flags, self.config.get_param(object_name, param_name)) parsed_params[param_name] = param_value obj = object_class(**parsed_params) self.objects_by_name[object_name] = obj return obj def fill_in_simobj_parameters(self, object_name, obj): """Fill in all references to other SimObjects in an objects parameters. This relies on all referenced objects having been created""" if object_name == 'Null': return NULL for param_name, param in obj.__class__._params.iteritems(): if issubclass(param.ptype, m5.objects.SimObject): if isinstance(param, m5.params.VectorParamDesc): param_values = self.config.get_param_vector(object_name, param_name) setattr(obj, param_name, [ self.objects_by_name[name] if name != 'Null' else m5.params.NULL for name in param_values ]) else: param_value = self.config.get_param(object_name, param_name) if param_value != 'Null': setattr(obj, param_name, self.objects_by_name[ param_value]) return obj def fill_in_children(self, object_name, obj): """Fill in the children of this object. This relies on all the referenced objects having been created""" children = self.config.get_object_children(object_name) for child_name, child_paths in children: param = obj.__class__._params.get(child_name, None) if child_name == 'Null': continue if isinstance(child_paths, list): child_list = [ self.objects_by_name[path] for path in child_paths ] else: child_list = self.objects_by_name[child_paths] obj.add_child(child_name, child_list) for path in to_list(child_paths): self.fill_in_children(path, self.objects_by_name[path]) return obj def parse_port_name(self, port): """Parse the name of a port""" m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', port) peer, peer_port, whole_index, index = m.groups() if index is not None: index = int(index) else: index = 0 return (peer, self.objects_by_name[peer], peer_port, index) def gather_port_connections(self, object_name, obj): """Gather all the port-to-port connections from the named object. Returns a list of (PortConnection, PortConnection) with unordered (wrt. master/slave) connection information""" if object_name == 'Null': return NULL parsed_ports = [] for port_name, port in obj.__class__._ports.iteritems(): # Assume that unnamed ports are unconnected peers = self.config.get_port_peers(object_name, port_name) for index, peer in zip(xrange(0, len(peers)), peers): parsed_ports.append(( PortConnection(object_name, port.name, index), PortConnection.from_string(peer))) return parsed_ports def bind_ports(self, connections): """Bind all ports from the given connection list. Note that the connection list *must* list all connections with both (slave,master) and (master,slave) orderings""" # Markup a dict of how many connections are made to each port. # This will be used to check that the next-to-be-made connection # has a suitable port index port_bind_indices = {} for from_port, to_port in connections: port_bind_indices[ (from_port.object_name, from_port.port_name)] = 0 def port_has_correct_index(port): return port_bind_indices[ (port.object_name, port.port_name)] == port.index def increment_port_index(port): port_bind_indices[ (port.object_name, port.port_name)] += 1 # Step through the sorted connections. Exactly one of # each (slave,master) and (master,slave) pairs will be # bindable because the connections are sorted. # For example: port_bind_indices # left right left right # a.b[0] -> d.f[1] 0 0 X # a.b[1] -> e.g 0 0 BIND! # e.g -> a.b[1] 1 X 0 # d.f[0] -> f.h 0 0 BIND! # d.f[1] -> a.b[0] 1 0 BIND! connections_to_make = [] for connection in sorted(connections): from_port, to_port = connection if (port_has_correct_index(from_port) and port_has_correct_index(to_port)): connections_to_make.append((from_port, to_port)) increment_port_index(from_port) increment_port_index(to_port) # Exactly half of the connections (ie. all of them, one per # direction) must now have been made if (len(connections_to_make) * 2) != len(connections): raise Exception('Port bindings can\'t be ordered') # Actually do the binding for from_port, to_port in connections_to_make: from_object = self.objects_by_name[from_port.object_name] to_object = self.objects_by_name[to_port.object_name] setattr(from_object, from_port.port_name, getattr(to_object, to_port.port_name)) def find_all_objects(self): """Find and build all SimObjects from the config file and connect their ports together as described. Does not instantiate system""" # Build SimObjects for all sections of the config file # populating not-SimObject-valued parameters for object_name in self.config.get_all_object_names(): self.find_object(object_name) # Add children to objects in the hierarchy from root self.fill_in_children('root', self.find_object('root')) # Now fill in SimObject-valued parameters in the knowledge that # this won't be interpreted as becoming the parent of objects # which are already in the root hierarchy for name, obj in self.objects_by_name.iteritems(): self.fill_in_simobj_parameters(name, obj) # Gather a list of all port-to-port connections connections = [] for name, obj in self.objects_by_name.iteritems(): connections += self.gather_port_connections(name, obj) # Find an acceptable order to bind those port connections and # bind them self.bind_ports(connections) class ConfigFile(object): def get_flags(self): return set() def load(self, config_file): """Load the named config file""" pass def get_all_object_names(self): """Get a list of all the SimObject paths in the configuration""" pass def get_param(self, object_name, param_name): """Get a single param or SimObject reference from the configuration as a string""" pass def get_param_vector(self, object_name, param_name): """Get a vector param or vector of SimObject references from the configuration as a list of strings""" pass def get_object_children(self, object_name): """Get a list of (name, paths) for each child of this object. paths is either a single string object path or a list of object paths""" pass def get_port_peers(self, object_name, port_name): """Get the list of connected port names (in the string form object.port(\[index\])?) of the port object_name.port_name""" pass class ConfigIniFile(ConfigFile): def __init__(self): self.parser = ConfigParser.ConfigParser() def load(self, config_file): self.parser.read(config_file) def get_all_object_names(self): return self.parser.sections() def get_param(self, object_name, param_name): return self.parser.get(object_name, param_name) def get_param_vector(self, object_name, param_name): return self.parser.get(object_name, param_name).split() def get_object_children(self, object_name): if self.parser.has_option(object_name, 'children'): children = self.parser.get(object_name, 'children') child_names = children.split() else: child_names = [] def make_path(child_name): if object_name == 'root': return child_name else: return '%s.%s' % (object_name, child_name) return [ (name, make_path(name)) for name in child_names ] def get_port_peers(self, object_name, port_name): if self.parser.has_option(object_name, port_name): peer_string = self.parser.get(object_name, port_name) return peer_string.split() else: return [] class ConfigJsonFile(ConfigFile): def __init__(self): pass def is_sim_object(self, node): return isinstance(node, dict) and 'path' in node def find_all_objects(self, node): if self.is_sim_object(node): self.object_dicts[node['path']] = node if isinstance(node, list): for elem in node: self.find_all_objects(elem) elif isinstance(node, dict): for elem in node.itervalues(): self.find_all_objects(elem) def load(self, config_file): root = json.load(open(config_file, 'r')) self.object_dicts = {} self.find_all_objects(root) def get_all_object_names(self): return sorted(self.object_dicts.keys()) def parse_param_string(self, node): if node is None: return "Null" elif self.is_sim_object(node): return node['path'] else: return str(node) def get_param(self, object_name, param_name): obj = self.object_dicts[object_name] return self.parse_param_string(obj[param_name]) def get_param_vector(self, object_name, param_name): obj = self.object_dicts[object_name] return [ self.parse_param_string(p) for p in obj[param_name] ] def get_object_children(self, object_name): """It is difficult to tell which elements are children in the JSON file as there is no explicit 'children' node. Take any element which is a full SimObject description or a list of SimObject descriptions. This will not work with a mixed list of references and descriptions but that's a scenario that isn't possible (very likely?) with gem5's binding/naming rules""" obj = self.object_dicts[object_name] children = [] for name, node in obj.iteritems(): if self.is_sim_object(node): children.append((name, node['path'])) elif isinstance(node, list) and node != [] and all([ self.is_sim_object(e) for e in node ]): children.append((name, [ e['path'] for e in node ])) return children def get_port_peers(self, object_name, port_name): """Get the 'peer' element of any node with 'peer' and 'role' elements""" obj = self.object_dicts[object_name] peers = [] if port_name in obj and 'peer' in obj[port_name] and \ 'role' in obj[port_name]: peers = to_list(obj[port_name]['peer']) return peers parser = argparse.ArgumentParser() parser.add_argument('config_file', metavar='config-file.ini', help='.ini configuration file to load and run') parser.add_argument('--checkpoint-dir', type=str, default=None, help='A checkpoint to directory to restore when starting ' 'the simulation') args = parser.parse_args(sys.argv[1:]) if args.config_file.endswith('.ini'): config = ConfigIniFile() config.load(args.config_file) else: config = ConfigJsonFile() config.load(args.config_file) ticks.fixGlobalFrequency() mgr = ConfigManager(config) mgr.find_all_objects() m5.instantiate(args.checkpoint_dir) exit_event = m5.simulate() print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()))