read_config.py revision 13774:a1be2a0c55f2
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