blob: 40c20ef50111dcddcb6f3d8f155327377db6510b [file] [log] [blame] [edit]
# 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()))