| # Copyright (c) 2014,2019 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. |
| |
| 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 list(m5.objects.__dict__.values()) |
| 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( |
| f"Can't parse string: {str(param)} for parameter class: {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() |
| _param = param.split(":") |
| (start, end) = _param[0:2] |
| if len(_param) == 2: |
| return m5.objects.AddrRange(start=int(start), end=int(end)) |
| else: |
| assert len(_param) > 2 |
| intlv_match = _param[2] |
| masks = [int(m) for m in _param[3:]] |
| return m5.objects.AddrRange( |
| start=int(start), |
| end=int(end), |
| masks=masks, |
| intlvMatch=int(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(f"{value:f}B/s") |
| |
| |
| # These parameters have trickier parsing from .ini files than might be |
| # expected |
| param_parsers = { |
| "Bool": simple_parser(), |
| "ParamValue": no_parser, |
| "NumericParamValue": simple_parser(cast=int), |
| "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 list(param_parsers.items()): |
| 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( |
| f"No SimObject type {object_type} is available to build: {object_name}" |
| ) |
| |
| object_class = sim_object_classes_by_name[object_type] |
| |
| parsed_params = {} |
| |
| for param_name, param in list(object_class._params.items()): |
| 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 list(obj.__class__._params.items()): |
| 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. requestor/responder) connection information""" |
| |
| if object_name == "Null": |
| return NULL |
| |
| parsed_ports = [] |
| for port_name, port in list(obj.__class__._ports.items()): |
| # Assume that unnamed ports are unconnected |
| peers = self.config.get_port_peers(object_name, port_name) |
| |
| for index, peer in zip(list(range(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 |
| (responder,requestor) and (requestor,responder) 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 (responder,requestor) and (requestor,responder) 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 list(self.objects_by_name.items()): |
| self.fill_in_simobj_parameters(name, obj) |
| |
| # Gather a list of all port-to-port connections |
| connections = [] |
| for name, obj in list(self.objects_by_name.items()): |
| 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 f"{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 list(node.values()): |
| 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 list(obj.items()): |
| 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())) |