blob: 1645dfcdc6604b20f368b1a3d83f55cf5c00d5db [file] [log] [blame]
# Copyright (c) 2021 The Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import m5
import m5.ticks
from m5.stats import addStatVisitor
from m5.stats.gem5stats import get_simstat
from m5.objects import Root
from m5.util import warn
import os
from typing import Optional, List, Tuple, Dict, Generator, Union
from .exit_event_generators import (
default_exit_generator,
default_switch_generator,
default_workbegin_generator,
default_workend_generator,
)
from .exit_event import ExitEvent
from ..components.boards.abstract_board import AbstractBoard
from ..components.processors.cpu_types import CPUTypes
class Simulator:
"""
This Simulator class is used to manage the execution of a gem5 simulation.
**Warning:** The simulate package is still in a beta state. The gem5
project does not guarantee the APIs within this package will remain
consistent in future across upcoming releases.
Example
-------
Examples using the Simulator class can be found under
`configs/example/gem5_library`.
The most basic run would be as follows:
```
simulator = Simulator(board=board)
simulator.run()
```
This will run a simulation and execute default behavior for exit events.
"""
def __init__(
self,
board: AbstractBoard,
full_system: bool = True,
on_exit_event: Optional[
Dict[Union[str, ExitEvent], Generator[Optional[bool], None, None]]
] = None,
expected_execution_order: Optional[List[ExitEvent]] = None,
) -> None:
"""
:param board: The board to be simulated.
:param full_system: Whether to run in full-system simulation or not. If
False, the simulation will run in Syscall-Execution mode. True by
default.
:param on_exit_event: An optional map to specify the generator to
execute on each exit event. The generator may yield a boolean which,
if True, will have the Simulator exit the run loop.
:param expected_execution_order: May be specified to check the exit
events come in a specified order. If the order specified is not
encountered (e.g., 'Workbegin', 'Workend', then 'Exit'), an Exception
is thrown. If this parameter is not specified, any ordering of exit
events is valid.
`on_exit_event` usage notes
---------------------------
The `on_exit_event` parameter specifies a Python generator for each
exit event. `next(<generator>)` is run each time an exit event. The
generator may yield a boolean. If this value of this boolean is True
the Simulator run loop will exit, otherwise
the Simulator run loop will continue execution. If the generator has
finished (i.e. a `StopIteration` exception is thrown when
`next(<generator>)` is executed), then the default behavior for that
exit event is run.
As an example, a user may specify their own exit event setup like so:
```
def unique_exit_event():
processor.switch()
yield False
m5.stats.dump()
yield False
yield True
simulator = Simulator(
board=board
on_exit_event = {
ExitEvent.Exit : unique_exit_event(),
},
)
```
This will execute `processor.switch()` the first time an exit event is
encountered, will dump gem5 statistics the second time an exit event is
encountered, and will terminate the Simulator run loop the third time.
Each exit event has a default behavior if none is specified by the
user. These are as follows:
* ExitEvent.EXIT: default_exit_list
* ExitEvent.CHECKPOINT: default_exit_list
* ExitEvent.FAIL : default_exit_list
* ExitEvent.SWITCHCPU: default_switch_list
* ExitEvent.WORKBEGIN: default_workbegin_list
* ExitEvent.WORKEND: default_workend_list
* ExitEvent.USER_INTERRUPT: default_exit_generator
* ExitEvent.MAX_TICK: default_exit_generator()
These generators can be found in the `exit_event_generator.py` module.
"""
warn(
"The simulate package is still in a beta state. The gem5 "
"project does not guarantee the APIs within this package will "
"remain consistent across upcoming releases."
)
# We specify a dictionary here outlining the default behavior for each
# exit event. Each exit event is mapped to a generator.
self._default_on_exit_dict = {
ExitEvent.EXIT: default_exit_generator(),
# TODO: Something else should be done here for CHECKPOINT
ExitEvent.CHECKPOINT: default_exit_generator(),
ExitEvent.FAIL: default_exit_generator(),
ExitEvent.SWITCHCPU: default_switch_generator(
processor=board.get_processor()
),
ExitEvent.WORKBEGIN: default_workbegin_generator(),
ExitEvent.WORKEND: default_workend_generator(),
ExitEvent.USER_INTERRUPT: default_exit_generator(),
ExitEvent.MAX_TICK: default_exit_generator(),
}
if on_exit_event:
self._on_exit_event = on_exit_event
else:
self._on_exit_event = self._default_on_exit_dict
self._instantiated = False
self._board = board
self._full_system = full_system
self._expected_execution_order = expected_execution_order
self._tick_stopwatch = []
self._last_exit_event = None
self._exit_event_count = 0
def get_stats(self) -> Dict:
"""
Obtain the current simulation statistics as a Dictionary, conforming
to a JSON-style schema.
**Warning:** Will throw an Exception if called before `run()`. The
board must be initialized before obtaining statistics
"""
if not self._instantiated:
raise Exception(
"Cannot obtain simulation statistics prior to inialization."
)
return get_simstat(self._root).to_json()
def add_text_stats_output(self, path: str) -> None:
"""
This function is used to set an output location for text stats. If
specified, when stats are dumped they will be output to this location
as a text file file, in addition to any other stats' output locations
specified.
:param path: That path in which the file should be output to.
"""
if not os.is_path_exists_or_creatable(path):
raise Exception(
f"Path '{path}' is is not a valid text stats output location."
)
addStatVisitor(path)
def add_json_stats_output(self, path: str) -> None:
"""
This function is used to set an output location for JSON. If specified,
when stats are dumped they will be output to this location as a JSON
file, in addition to any other stats' output locations specified.
:param path: That path in which the JSON should be output to.
"""
if not os.is_path_exists_or_creatable(path):
raise Exception(
f"Path '{path}' is is not a valid JSON output location."
)
addStatVisitor(f"json://{path}")
def get_last_exit_event_cause(self) -> str:
"""
Returns the last exit event cause.
"""
return self._last_exit_event.getCause()
def get_current_tick(self) -> int:
"""
Returns the current tick.
"""
return m5.curTick()
def get_tick_stopwatch(self) -> List[Tuple[ExitEvent, int]]:
"""
Returns a list of tuples, which each tuple specifying an exit event
and the ticks at that event.
"""
return self._tick_stopwatch
def get_roi_ticks(self) -> List[int]:
"""
Returns a list of the tick counts for every ROI encountered (specified
as a region of code between a Workbegin and Workend exit event).
"""
start = 0
to_return = []
for (exit_event, tick) in self._tick_stopwatch:
if exit_event == ExitEvent.WORKBEGIN:
start = tick
elif exit_event == ExitEvent.WORKEND:
to_return.append(tick - start)
return to_return
def _instantiate(self) -> None:
"""
This method will instantiate the board and carry out necessary
boilerplate code before the instantiation such as setting up root and
setting the sim_quantum (if running in KVM mode).
"""
if not self._instantiated:
root = Root(full_system=self._full_system, board=self._board)
# We take a copy of the Root in case it's required elsewhere
# (for example, in `get_stats()`).
self._root = root
if CPUTypes.KVM in [
core.get_type()
for core in self._board.get_processor().get_cores()
]:
m5.ticks.fixGlobalFrequency()
root.sim_quantum = m5.ticks.fromSeconds(0.001)
m5.instantiate()
self._instantiated = True
def run(self, max_ticks: int = m5.MaxTick) -> None:
"""
This function will start or continue the simulator run and handle exit
events accordingly.
:param max_ticks: The maximum number of ticks to execute per simulation
run. If this max_ticks value is met, a MAX_TICK exit event is
received, if another simulation exit event is met the tick count is
reset. This is the **maximum number of ticks per simululation run**.
"""
# We instantiate the board if it has not already been instantiated.
self._instantiate()
# This while loop will continue until an a generator yields True.
while True:
self._last_exit_event = m5.simulate(max_ticks)
# Translate the exit event cause to the exit event enum.
exit_enum = ExitEvent.translate_exit_status(
self.get_last_exit_event_cause()
)
# Check to see the run is corresponding to the expected execution
# order (assuming this check is demanded by the user).
if self._expected_execution_order:
expected_enum = self._expected_execution_order[
self._exit_event_count
]
if exit_enum.value != expected_enum.value:
raise Exception(
f"Expected a '{expected_enum.value}' exit event but a "
f"'{exit_enum.value}' exit event was encountered."
)
# Record the current tick and exit event enum.
self._tick_stopwatch.append((exit_enum, self.get_current_tick()))
try:
# If the user has specified their own generator for this exit
# event, use it.
exit_on_completion = next(self._on_exit_event[exit_enum])
except StopIteration:
# If the user's generator has ended, throw a warning and use
# the default generator for this exit event.
warn(
"User-specified generator for the exit event "
f"'{exit_enum.value}' has ended. Using the default "
"generator."
)
exit_on_completion = next(
self._default_on_exit_dict[exit_enum]
)
except KeyError:
# If the user has not specified their own generator for this
# exit event, use the default.
exit_on_completion = next(
self._default_on_exit_dict[exit_enum]
)
self._exit_event_count += 1
# If the generator returned True we will return from the Simulator
# run loop.
if exit_on_completion:
return