simulate.py revision 13714:35636064b7a1
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
43from __future__ import print_function
44
45import atexit
46import os
47import sys
48
49# import the wrapped C++ functions
50import _m5.drain
51import _m5.core
52from _m5.stats import updateEvents as updateStatEvents
53
54from . import stats
55from . import SimObject
56from . import ticks
57from . import objects
58from m5.util.dot_writer import do_dot, do_dvfs_dot
59
60from .util import fatal
61from .util import attrdict
62
63# define a MaxTick parameter, unsigned 64 bit
64MaxTick = 2**64 - 1
65
66_memory_modes = {
67    "atomic" : objects.params.atomic,
68    "timing" : objects.params.timing,
69    "atomic_noncaching" : objects.params.atomic_noncaching,
70    }
71
72_drain_manager = _m5.drain.DrainManager.instance()
73
74# The final hook to generate .ini files.  Called from the user script
75# once the config is built.
76def instantiate(ckpt_dir=None):
77    from m5 import options
78
79    root = objects.Root.getInstance()
80
81    if not root:
82        fatal("Need to instantiate Root() before calling instantiate()")
83
84    # we need to fix the global frequency
85    ticks.fixGlobalFrequency()
86
87    # Make sure SimObject-valued params are in the configuration
88    # hierarchy so we catch them with future descendants() walks
89    for obj in root.descendants(): obj.adoptOrphanParams()
90
91    # Unproxy in sorted order for determinism
92    for obj in root.descendants(): obj.unproxyParams()
93
94    if options.dump_config:
95        ini_file = open(os.path.join(options.outdir, options.dump_config), 'w')
96        # Print ini sections in sorted order for easier diffing
97        for obj in sorted(root.descendants(), key=lambda o: o.path()):
98            obj.print_ini(ini_file)
99        ini_file.close()
100
101    if options.json_config:
102        try:
103            import json
104            json_file = open(
105                os.path.join(options.outdir, options.json_config), 'w')
106            d = root.get_config_as_dict()
107            json.dump(d, json_file, indent=4)
108            json_file.close()
109        except ImportError:
110            pass
111
112    do_dot(root, options.outdir, options.dot_config)
113
114    # Initialize the global statistics
115    stats.initSimStats()
116
117    # Create the C++ sim objects and connect ports
118    for obj in root.descendants(): obj.createCCObject()
119    for obj in root.descendants(): obj.connectPorts()
120
121    # Do a second pass to finish initializing the sim objects
122    for obj in root.descendants(): obj.init()
123
124    # Do a third pass to initialize statistics
125    for obj in root.descendants(): obj.regStats()
126
127    # Do a fourth pass to initialize probe points
128    for obj in root.descendants(): obj.regProbePoints()
129
130    # Do a fifth pass to connect probe listeners
131    for obj in root.descendants(): obj.regProbeListeners()
132
133    # We want to generate the DVFS diagram for the system. This can only be
134    # done once all of the CPP objects have been created and initialised so
135    # that we are able to figure out which object belongs to which domain.
136    if options.dot_dvfs_config:
137        do_dvfs_dot(root, options.outdir, options.dot_dvfs_config)
138
139    # We're done registering statistics.  Enable the stats package now.
140    stats.enable()
141
142    # Restore checkpoint (if any)
143    if ckpt_dir:
144        _drain_manager.preCheckpointRestore()
145        ckpt = _m5.core.getCheckpoint(ckpt_dir)
146        _m5.core.unserializeGlobals(ckpt);
147        for obj in root.descendants(): obj.loadState(ckpt)
148    else:
149        for obj in root.descendants(): obj.initState()
150
151    # Check to see if any of the stat events are in the past after resuming from
152    # a checkpoint, If so, this call will shift them to be at a valid time.
153    updateStatEvents()
154
155need_startup = True
156def simulate(*args, **kwargs):
157    global need_startup
158
159    if need_startup:
160        root = objects.Root.getInstance()
161        for obj in root.descendants(): obj.startup()
162        need_startup = False
163
164        # Python exit handlers happen in reverse order.
165        # We want to dump stats last.
166        atexit.register(stats.dump)
167
168        # register our C++ exit callback function with Python
169        atexit.register(_m5.core.doExitCleanup)
170
171        # Reset to put the stats in a consistent state.
172        stats.reset()
173
174    if _drain_manager.isDrained():
175        _drain_manager.resume()
176
177    return _m5.event.simulate(*args, **kwargs)
178
179def drain():
180    """Drain the simulator in preparation of a checkpoint or memory mode
181    switch.
182
183    This operation is a no-op if the simulator is already in the
184    Drained state.
185
186    """
187
188    # Try to drain all objects. Draining might not be completed unless
189    # all objects return that they are drained on the first call. This
190    # is because as objects drain they may cause other objects to no
191    # longer be drained.
192    def _drain():
193        # Try to drain the system. The drain is successful if all
194        # objects are done without simulation. We need to simulate
195        # more if not.
196        if _drain_manager.tryDrain():
197            return True
198
199        # WARNING: if a valid exit event occurs while draining, it
200        # will not get returned to the user script
201        exit_event = _m5.event.simulate()
202        while exit_event.getCause() != 'Finished drain':
203            exit_event = simulate()
204
205        return False
206
207    # Don't try to drain a system that is already drained
208    is_drained = _drain_manager.isDrained()
209    while not is_drained:
210        is_drained = _drain()
211
212    assert _drain_manager.isDrained(), "Drain state inconsistent"
213
214def memWriteback(root):
215    for obj in root.descendants():
216        obj.memWriteback()
217
218def memInvalidate(root):
219    for obj in root.descendants():
220        obj.memInvalidate()
221
222def checkpoint(dir):
223    root = objects.Root.getInstance()
224    if not isinstance(root, objects.Root):
225        raise TypeError("Checkpoint must be called on a root object.")
226
227    drain()
228    memWriteback(root)
229    print("Writing checkpoint")
230    _m5.core.serializeAll(dir)
231
232def _changeMemoryMode(system, mode):
233    if not isinstance(system, (objects.Root, objects.System)):
234        raise TypeError("Parameter of type '%s'.  Must be type %s or %s." % \
235              (type(system), objects.Root, objects.System))
236    if system.getMemoryMode() != mode:
237        system.setMemoryMode(mode)
238    else:
239        print("System already in target mode. Memory mode unchanged.")
240
241def switchCpus(system, cpuList, verbose=True):
242    """Switch CPUs in a system.
243
244    Note: This method may switch the memory mode of the system if that
245    is required by the CPUs. It may also flush all caches in the
246    system.
247
248    Arguments:
249      system -- Simulated system.
250      cpuList -- (old_cpu, new_cpu) tuples
251    """
252
253    if verbose:
254        print("switching cpus")
255
256    if not isinstance(cpuList, list):
257        raise RuntimeError("Must pass a list to this function")
258    for item in cpuList:
259        if not isinstance(item, tuple) or len(item) != 2:
260            raise RuntimeError("List must have tuples of (oldCPU,newCPU)")
261
262    old_cpus = [old_cpu for old_cpu, new_cpu in cpuList]
263    new_cpus = [new_cpu for old_cpu, new_cpu in cpuList]
264    old_cpu_set = set(old_cpus)
265    memory_mode_name = new_cpus[0].memory_mode()
266    for old_cpu, new_cpu in cpuList:
267        if not isinstance(old_cpu, objects.BaseCPU):
268            raise TypeError("%s is not of type BaseCPU" % old_cpu)
269        if not isinstance(new_cpu, objects.BaseCPU):
270            raise TypeError("%s is not of type BaseCPU" % new_cpu)
271        if new_cpu in old_cpu_set:
272            raise RuntimeError(
273                "New CPU (%s) is in the list of old CPUs." % (old_cpu,))
274        if not new_cpu.switchedOut():
275            raise RuntimeError("New CPU (%s) is already active." % (new_cpu,))
276        if not new_cpu.support_take_over():
277            raise RuntimeError(
278                "New CPU (%s) does not support CPU handover." % (old_cpu,))
279        if new_cpu.memory_mode() != memory_mode_name:
280            raise RuntimeError(
281                "%s and %s require different memory modes." % (new_cpu,
282                                                               new_cpus[0]))
283        if old_cpu.switchedOut():
284            raise RuntimeError("Old CPU (%s) is inactive." % (new_cpu,))
285        if not old_cpu.support_take_over():
286            raise RuntimeError(
287                "Old CPU (%s) does not support CPU handover." % (old_cpu,))
288
289    try:
290        memory_mode = _memory_modes[memory_mode_name]
291    except KeyError:
292        raise RuntimeError("Invalid memory mode (%s)" % memory_mode_name)
293
294    drain()
295
296    # Now all of the CPUs are ready to be switched out
297    for old_cpu, new_cpu in cpuList:
298        old_cpu.switchOut()
299
300    # Change the memory mode if required. We check if this is needed
301    # to avoid printing a warning if no switch was performed.
302    if system.getMemoryMode() != memory_mode:
303        # Flush the memory system if we are switching to a memory mode
304        # that disables caches. This typically happens when switching to a
305        # hardware virtualized CPU.
306        if memory_mode == objects.params.atomic_noncaching:
307            memWriteback(system)
308            memInvalidate(system)
309
310        _changeMemoryMode(system, memory_mode)
311
312    for old_cpu, new_cpu in cpuList:
313        new_cpu.takeOverFrom(old_cpu)
314
315def notifyFork(root):
316    for obj in root.descendants():
317        obj.notifyFork()
318
319fork_count = 0
320def fork(simout="%(parent)s.f%(fork_seq)i"):
321    """Fork the simulator.
322
323    This function forks the simulator. After forking the simulator,
324    the child process gets its output files redirected to a new output
325    directory. The default name of the output directory is the same as
326    the parent with the suffix ".fN" added where N is the fork
327    sequence number. The name of the output directory can be
328    overridden using the simout keyword argument.
329
330    Output file formatting dictionary:
331      parent -- Path to the parent process's output directory.
332      fork_seq -- Fork sequence number.
333      pid -- PID of the child process.
334
335    Keyword Arguments:
336      simout -- New simulation output directory.
337
338    Return Value:
339      pid of the child process or 0 if running in the child.
340    """
341    from m5 import options
342    global fork_count
343
344    if not _m5.core.listenersDisabled():
345        raise RuntimeError("Can not fork a simulator with listeners enabled")
346
347    drain()
348
349    try:
350        pid = os.fork()
351    except OSError as e:
352        raise e
353
354    if pid == 0:
355        # In child, notify objects of the fork
356        root = objects.Root.getInstance()
357        notifyFork(root)
358        # Setup a new output directory
359        parent = options.outdir
360        options.outdir = simout % {
361                "parent" : parent,
362                "fork_seq" : fork_count,
363                "pid" : os.getpid(),
364                }
365        _m5.core.setOutputDir(options.outdir)
366    else:
367        fork_count += 1
368
369    return pid
370
371from _m5.core import disableAllListeners, listenersDisabled
372from _m5.core import listenersLoopbackOnly
373from _m5.core import curTick
374