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 stats._bindStatHierarchy(root) 128 root.regStats() 129 130 # Do a fourth pass to initialize probe points 131 for obj in root.descendants(): obj.regProbePoints() 132 133 # Do a fifth pass to connect probe listeners 134 for obj in root.descendants(): obj.regProbeListeners() 135 136 # We want to generate the DVFS diagram for the system. This can only be 137 # done once all of the CPP objects have been created and initialised so 138 # that we are able to figure out which object belongs to which domain. 139 if options.dot_dvfs_config: 140 do_dvfs_dot(root, options.outdir, options.dot_dvfs_config) 141 142 # We're done registering statistics. Enable the stats package now. 143 stats.enable() 144 145 # Restore checkpoint (if any) 146 if ckpt_dir: 147 _drain_manager.preCheckpointRestore() 148 ckpt = _m5.core.getCheckpoint(ckpt_dir) 149 _m5.core.unserializeGlobals(ckpt); 150 for obj in root.descendants(): obj.loadState(ckpt) 151 else: 152 for obj in root.descendants(): obj.initState() 153 154 # Check to see if any of the stat events are in the past after resuming from 155 # a checkpoint, If so, this call will shift them to be at a valid time. 156 updateStatEvents() 157 158need_startup = True 159def simulate(*args, **kwargs): 160 global need_startup 161 162 if need_startup: 163 root = objects.Root.getInstance() 164 for obj in root.descendants(): obj.startup() 165 need_startup = False 166 167 # Python exit handlers happen in reverse order. 168 # We want to dump stats last. 169 atexit.register(stats.dump) 170 171 # register our C++ exit callback function with Python 172 atexit.register(_m5.core.doExitCleanup) 173 174 # Reset to put the stats in a consistent state. 175 stats.reset() 176 177 if _drain_manager.isDrained(): 178 _drain_manager.resume() 179 180 return _m5.event.simulate(*args, **kwargs) 181 182def drain(): 183 """Drain the simulator in preparation of a checkpoint or memory mode 184 switch. 185 186 This operation is a no-op if the simulator is already in the 187 Drained state. 188 189 """ 190 191 # Try to drain all objects. Draining might not be completed unless 192 # all objects return that they are drained on the first call. This 193 # is because as objects drain they may cause other objects to no 194 # longer be drained. 195 def _drain(): 196 # Try to drain the system. The drain is successful if all 197 # objects are done without simulation. We need to simulate 198 # more if not. 199 if _drain_manager.tryDrain(): 200 return True 201 202 # WARNING: if a valid exit event occurs while draining, it 203 # will not get returned to the user script 204 exit_event = _m5.event.simulate() 205 while exit_event.getCause() != 'Finished drain': 206 exit_event = simulate() 207 208 return False 209 210 # Don't try to drain a system that is already drained 211 is_drained = _drain_manager.isDrained() 212 while not is_drained: 213 is_drained = _drain() 214 215 assert _drain_manager.isDrained(), "Drain state inconsistent" 216 217def memWriteback(root): 218 for obj in root.descendants(): 219 obj.memWriteback() 220 221def memInvalidate(root): 222 for obj in root.descendants(): 223 obj.memInvalidate() 224 225def checkpoint(dir): 226 root = objects.Root.getInstance() 227 if not isinstance(root, objects.Root): 228 raise TypeError("Checkpoint must be called on a root object.") 229 230 drain() 231 memWriteback(root) 232 print("Writing checkpoint") 233 _m5.core.serializeAll(dir) 234 235def _changeMemoryMode(system, mode): 236 if not isinstance(system, (objects.Root, objects.System)): 237 raise TypeError("Parameter of type '%s'. Must be type %s or %s." % \ 238 (type(system), objects.Root, objects.System)) 239 if system.getMemoryMode() != mode: 240 system.setMemoryMode(mode) 241 else: 242 print("System already in target mode. Memory mode unchanged.") 243 244def switchCpus(system, cpuList, verbose=True): 245 """Switch CPUs in a system. 246 247 Note: This method may switch the memory mode of the system if that 248 is required by the CPUs. It may also flush all caches in the 249 system. 250 251 Arguments: 252 system -- Simulated system. 253 cpuList -- (old_cpu, new_cpu) tuples 254 """ 255 256 if verbose: 257 print("switching cpus") 258 259 if not isinstance(cpuList, list): 260 raise RuntimeError("Must pass a list to this function") 261 for item in cpuList: 262 if not isinstance(item, tuple) or len(item) != 2: 263 raise RuntimeError("List must have tuples of (oldCPU,newCPU)") 264 265 old_cpus = [old_cpu for old_cpu, new_cpu in cpuList] 266 new_cpus = [new_cpu for old_cpu, new_cpu in cpuList] 267 old_cpu_set = set(old_cpus) 268 memory_mode_name = new_cpus[0].memory_mode() 269 for old_cpu, new_cpu in cpuList: 270 if not isinstance(old_cpu, objects.BaseCPU): 271 raise TypeError("%s is not of type BaseCPU" % old_cpu) 272 if not isinstance(new_cpu, objects.BaseCPU): 273 raise TypeError("%s is not of type BaseCPU" % new_cpu) 274 if new_cpu in old_cpu_set: 275 raise RuntimeError( 276 "New CPU (%s) is in the list of old CPUs." % (old_cpu,)) 277 if not new_cpu.switchedOut(): 278 raise RuntimeError("New CPU (%s) is already active." % (new_cpu,)) 279 if not new_cpu.support_take_over(): 280 raise RuntimeError( 281 "New CPU (%s) does not support CPU handover." % (old_cpu,)) 282 if new_cpu.memory_mode() != memory_mode_name: 283 raise RuntimeError( 284 "%s and %s require different memory modes." % (new_cpu, 285 new_cpus[0])) 286 if old_cpu.switchedOut(): 287 raise RuntimeError("Old CPU (%s) is inactive." % (new_cpu,)) 288 if not old_cpu.support_take_over(): 289 raise RuntimeError( 290 "Old CPU (%s) does not support CPU handover." % (old_cpu,)) 291 292 try: 293 memory_mode = _memory_modes[memory_mode_name] 294 except KeyError: 295 raise RuntimeError("Invalid memory mode (%s)" % memory_mode_name) 296 297 drain() 298 299 # Now all of the CPUs are ready to be switched out 300 for old_cpu, new_cpu in cpuList: 301 old_cpu.switchOut() 302 303 # Change the memory mode if required. We check if this is needed 304 # to avoid printing a warning if no switch was performed. 305 if system.getMemoryMode() != memory_mode: 306 # Flush the memory system if we are switching to a memory mode 307 # that disables caches. This typically happens when switching to a 308 # hardware virtualized CPU. 309 if memory_mode == objects.params.atomic_noncaching: 310 memWriteback(system) 311 memInvalidate(system) 312 313 _changeMemoryMode(system, memory_mode) 314 315 for old_cpu, new_cpu in cpuList: 316 new_cpu.takeOverFrom(old_cpu) 317 318def notifyFork(root): 319 for obj in root.descendants(): 320 obj.notifyFork() 321 322fork_count = 0 323def fork(simout="%(parent)s.f%(fork_seq)i"): 324 """Fork the simulator. 325 326 This function forks the simulator. After forking the simulator, 327 the child process gets its output files redirected to a new output 328 directory. The default name of the output directory is the same as 329 the parent with the suffix ".fN" added where N is the fork 330 sequence number. The name of the output directory can be 331 overridden using the simout keyword argument. 332 333 Output file formatting dictionary: 334 parent -- Path to the parent process's output directory. 335 fork_seq -- Fork sequence number. 336 pid -- PID of the child process. 337 338 Keyword Arguments: 339 simout -- New simulation output directory. 340 341 Return Value: 342 pid of the child process or 0 if running in the child. 343 """ 344 from m5 import options 345 global fork_count 346 347 if not _m5.core.listenersDisabled(): 348 raise RuntimeError("Can not fork a simulator with listeners enabled") 349 350 drain() 351 352 try: 353 pid = os.fork() 354 except OSError as e: 355 raise e 356 357 if pid == 0: 358 # In child, notify objects of the fork 359 root = objects.Root.getInstance() 360 notifyFork(root) 361 # Setup a new output directory 362 parent = options.outdir 363 options.outdir = simout % { 364 "parent" : parent, 365 "fork_seq" : fork_count, 366 "pid" : os.getpid(), 367 } 368 _m5.core.setOutputDir(options.outdir) 369 else: 370 fork_count += 1 371 372 return pid 373 374from _m5.core import disableAllListeners, listenersDisabled 375from _m5.core import listenersLoopbackOnly 376from _m5.core import curTick 377