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