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