simulate.py revision 11362:04966a265ff9
1# Copyright (c) 2012 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# Copyright (c) 2005 The Regents of The University of Michigan
14# Copyright (c) 2010 Advanced Micro Devices, Inc.
15# All rights reserved.
16#
17# Redistribution and use in source and binary forms, with or without
18# modification, are permitted provided that the following conditions are
19# met: redistributions of source code must retain the above copyright
20# notice, this list of conditions and the following disclaimer;
21# redistributions in binary form must reproduce the above copyright
22# notice, this list of conditions and the following disclaimer in the
23# documentation and/or other materials provided with the distribution;
24# neither the name of the copyright holders nor the names of its
25# contributors may be used to endorse or promote products derived from
26# this software without specific prior written permission.
27#
28# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39#
40# Authors: Nathan Binkert
41#          Steve Reinhardt
42
43import atexit
44import os
45import sys
46
47# import the SWIG-wrapped main C++ functions
48import internal
49import core
50import stats
51import SimObject
52import ticks
53import objects
54from m5.util.dot_writer import do_dot
55from m5.internal.stats import updateEvents as updateStatEvents
56
57from util import fatal
58from util import attrdict
59
60# define a MaxTick parameter, unsigned 64 bit
61MaxTick = 2**64 - 1
62
63_memory_modes = {
64    "atomic" : objects.params.atomic,
65    "timing" : objects.params.timing,
66    "atomic_noncaching" : objects.params.atomic_noncaching,
67    }
68
69_drain_manager = internal.drain.DrainManager.instance()
70
71# The final hook to generate .ini files.  Called from the user script
72# once the config is built.
73def instantiate(ckpt_dir=None):
74    from m5 import options
75
76    root = objects.Root.getInstance()
77
78    if not root:
79        fatal("Need to instantiate Root() before calling instantiate()")
80
81    # we need to fix the global frequency
82    ticks.fixGlobalFrequency()
83
84    # Make sure SimObject-valued params are in the configuration
85    # hierarchy so we catch them with future descendants() walks
86    for obj in root.descendants(): obj.adoptOrphanParams()
87
88    # Unproxy in sorted order for determinism
89    for obj in root.descendants(): obj.unproxyParams()
90
91    if options.dump_config:
92        ini_file = file(os.path.join(options.outdir, options.dump_config), 'w')
93        # Print ini sections in sorted order for easier diffing
94        for obj in sorted(root.descendants(), key=lambda o: o.path()):
95            obj.print_ini(ini_file)
96        ini_file.close()
97
98    if options.json_config:
99        try:
100            import json
101            json_file = file(os.path.join(options.outdir, options.json_config), 'w')
102            d = root.get_config_as_dict()
103            json.dump(d, json_file, indent=4)
104            json_file.close()
105        except ImportError:
106            pass
107
108    do_dot(root, options.outdir, options.dot_config)
109
110    # Initialize the global statistics
111    stats.initSimStats()
112
113    # Create the C++ sim objects and connect ports
114    for obj in root.descendants(): obj.createCCObject()
115    for obj in root.descendants(): obj.connectPorts()
116
117    # Do a second pass to finish initializing the sim objects
118    for obj in root.descendants(): obj.init()
119
120    # Do a third pass to initialize statistics
121    for obj in root.descendants(): obj.regStats()
122
123    # Do a fourth pass to initialize probe points
124    for obj in root.descendants(): obj.regProbePoints()
125
126    # Do a fifth pass to connect probe listeners
127    for obj in root.descendants(): obj.regProbeListeners()
128
129    # We're done registering statistics.  Enable the stats package now.
130    stats.enable()
131
132    # Restore checkpoint (if any)
133    if ckpt_dir:
134        _drain_manager.preCheckpointRestore()
135        ckpt = internal.core.getCheckpoint(ckpt_dir)
136        internal.core.unserializeGlobals(ckpt);
137        for obj in root.descendants(): obj.loadState(ckpt)
138    else:
139        for obj in root.descendants(): obj.initState()
140
141    # Check to see if any of the stat events are in the past after resuming from
142    # a checkpoint, If so, this call will shift them to be at a valid time.
143    updateStatEvents()
144
145need_startup = True
146def simulate(*args, **kwargs):
147    global need_startup
148
149    if need_startup:
150        root = objects.Root.getInstance()
151        for obj in root.descendants(): obj.startup()
152        need_startup = False
153
154        # Python exit handlers happen in reverse order.
155        # We want to dump stats last.
156        atexit.register(stats.dump)
157
158        # register our C++ exit callback function with Python
159        atexit.register(internal.core.doExitCleanup)
160
161        # Reset to put the stats in a consistent state.
162        stats.reset()
163
164    if _drain_manager.isDrained():
165        _drain_manager.resume()
166
167    return internal.event.simulate(*args, **kwargs)
168
169# Export curTick to user script.
170def curTick():
171    return internal.core.curTick()
172
173def drain():
174    """Drain the simulator in preparation of a checkpoint or memory mode
175    switch.
176
177    This operation is a no-op if the simulator is already in the
178    Drained state.
179
180    """
181
182    # Try to drain all objects. Draining might not be completed unless
183    # all objects return that they are drained on the first call. This
184    # is because as objects drain they may cause other objects to no
185    # longer be drained.
186    def _drain():
187        # Try to drain the system. The drain is successful if all
188        # objects are done without simulation. We need to simulate
189        # more if not.
190        if _drain_manager.tryDrain():
191            return True
192
193        # WARNING: if a valid exit event occurs while draining, it
194        # will not get returned to the user script
195        exit_event = internal.event.simulate()
196        while exit_event.getCause() != 'Finished drain':
197            exit_event = simulate()
198
199        return False
200
201    # Don't try to drain a system that is already drained
202    is_drained = _drain_manager.isDrained()
203    while not is_drained:
204        is_drained = _drain()
205
206    assert _drain_manager.isDrained(), "Drain state inconsistent"
207
208def memWriteback(root):
209    for obj in root.descendants():
210        obj.memWriteback()
211
212def memInvalidate(root):
213    for obj in root.descendants():
214        obj.memInvalidate()
215
216def checkpoint(dir):
217    root = objects.Root.getInstance()
218    if not isinstance(root, objects.Root):
219        raise TypeError, "Checkpoint must be called on a root object."
220
221    drain()
222    memWriteback(root)
223    print "Writing checkpoint"
224    internal.core.serializeAll(dir)
225
226def _changeMemoryMode(system, mode):
227    if not isinstance(system, (objects.Root, objects.System)):
228        raise TypeError, "Parameter of type '%s'.  Must be type %s or %s." % \
229              (type(system), objects.Root, objects.System)
230    if system.getMemoryMode() != mode:
231        system.setMemoryMode(mode)
232    else:
233        print "System already in target mode. Memory mode unchanged."
234
235def switchCpus(system, cpuList, verbose=True):
236    """Switch CPUs in a system.
237
238    Note: This method may switch the memory mode of the system if that
239    is required by the CPUs. It may also flush all caches in the
240    system.
241
242    Arguments:
243      system -- Simulated system.
244      cpuList -- (old_cpu, new_cpu) tuples
245    """
246
247    if verbose:
248        print "switching cpus"
249
250    if not isinstance(cpuList, list):
251        raise RuntimeError, "Must pass a list to this function"
252    for item in cpuList:
253        if not isinstance(item, tuple) or len(item) != 2:
254            raise RuntimeError, "List must have tuples of (oldCPU,newCPU)"
255
256    old_cpus = [old_cpu for old_cpu, new_cpu in cpuList]
257    new_cpus = [new_cpu for old_cpu, new_cpu in cpuList]
258    old_cpu_set = set(old_cpus)
259    memory_mode_name = new_cpus[0].memory_mode()
260    for old_cpu, new_cpu in cpuList:
261        if not isinstance(old_cpu, objects.BaseCPU):
262            raise TypeError, "%s is not of type BaseCPU" % old_cpu
263        if not isinstance(new_cpu, objects.BaseCPU):
264            raise TypeError, "%s is not of type BaseCPU" % new_cpu
265        if new_cpu in old_cpu_set:
266            raise RuntimeError, \
267                "New CPU (%s) is in the list of old CPUs." % (old_cpu,)
268        if not new_cpu.switchedOut():
269            raise RuntimeError, \
270                "New CPU (%s) is already active." % (new_cpu,)
271        if not new_cpu.support_take_over():
272            raise RuntimeError, \
273                "New CPU (%s) does not support CPU handover." % (old_cpu,)
274        if new_cpu.memory_mode() != memory_mode_name:
275            raise RuntimeError, \
276                "%s and %s require different memory modes." % (new_cpu,
277                                                               new_cpus[0])
278        if old_cpu.switchedOut():
279            raise RuntimeError, \
280                "Old CPU (%s) is inactive." % (new_cpu,)
281        if not old_cpu.support_take_over():
282            raise RuntimeError, \
283                "Old CPU (%s) does not support CPU handover." % (old_cpu,)
284
285    try:
286        memory_mode = _memory_modes[memory_mode_name]
287    except KeyError:
288        raise RuntimeError, "Invalid memory mode (%s)" % memory_mode_name
289
290    drain()
291
292    # Now all of the CPUs are ready to be switched out
293    for old_cpu, new_cpu in cpuList:
294        old_cpu.switchOut()
295
296    # Change the memory mode if required. We check if this is needed
297    # to avoid printing a warning if no switch was performed.
298    if system.getMemoryMode() != memory_mode:
299        # Flush the memory system if we are switching to a memory mode
300        # that disables caches. This typically happens when switching to a
301        # hardware virtualized CPU.
302        if memory_mode == objects.params.atomic_noncaching:
303            memWriteback(system)
304            memInvalidate(system)
305
306        _changeMemoryMode(system, memory_mode)
307
308    for old_cpu, new_cpu in cpuList:
309        new_cpu.takeOverFrom(old_cpu)
310
311def notifyFork(root):
312    for obj in root.descendants():
313        obj.notifyFork()
314
315fork_count = 0
316def fork(simout="%(parent)s.f%(fork_seq)i"):
317    """Fork the simulator.
318
319    This function forks the simulator. After forking the simulator,
320    the child process gets its output files redirected to a new output
321    directory. The default name of the output directory is the same as
322    the parent with the suffix ".fN" added where N is the fork
323    sequence number. The name of the output directory can be
324    overridden using the simout keyword argument.
325
326    Output file formatting dictionary:
327      parent -- Path to the parent process's output directory.
328      fork_seq -- Fork sequence number.
329      pid -- PID of the child process.
330
331    Keyword Arguments:
332      simout -- New simulation output directory.
333
334    Return Value:
335      pid of the child process or 0 if running in the child.
336    """
337    from m5 import options
338    global fork_count
339
340    if not internal.core.listenersDisabled():
341        raise RuntimeError, "Can not fork a simulator with listeners enabled"
342
343    drain()
344
345    try:
346        pid = os.fork()
347    except OSError, e:
348        raise e
349
350    if pid == 0:
351        # In child, notify objects of the fork
352        root = objects.Root.getInstance()
353        notifyFork(root)
354        # Setup a new output directory
355        parent = options.outdir
356        options.outdir = simout % {
357                "parent" : parent,
358                "fork_seq" : fork_count,
359                "pid" : os.getpid(),
360                }
361        core.setOutputDir(options.outdir)
362    else:
363        fork_count += 1
364
365    return pid
366
367from internal.core import disableAllListeners
368from internal.core import listenersDisabled
369