mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 02:00:45 +00:00
gem5: Addition of gem5 simulation platform
This commit adds a gem5 simulation platform. The platform is responsible for starting the gem5 simulation. Changes to be committed: modified: devlib/__init__.py modified: devlib/platform/__init__.py new file: devlib/platform/gem5.py
This commit is contained in:
parent
29a7940731
commit
e9cf93e754
@ -7,6 +7,7 @@ from devlib.module import get_module, register_module
|
||||
|
||||
from devlib.platform import Platform
|
||||
from devlib.platform.arm import TC2, Juno, JunoEnergyInstrument
|
||||
from devlib.platform.gem5 import Gem5SimulationPlatform
|
||||
|
||||
from devlib.instrument import Instrument, InstrumentChannel, Measurement, MeasurementsCsv
|
||||
from devlib.instrument import MEASUREMENT_TYPES, INSTANTANEOUS, CONTINUOUS
|
||||
|
@ -48,6 +48,11 @@ class Platform(object):
|
||||
self.name = self.model
|
||||
self._validate()
|
||||
|
||||
def setup(self, target):
|
||||
# May be overwritten by subclasses to provide platform-specific
|
||||
# setup procedures.
|
||||
pass
|
||||
|
||||
def _set_core_clusters_from_core_names(self):
|
||||
self.core_clusters = []
|
||||
clusters = []
|
||||
|
282
devlib/platform/gem5.py
Normal file
282
devlib/platform/gem5.py
Normal file
@ -0,0 +1,282 @@
|
||||
# Copyright 2016 ARM Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
import time
|
||||
import types
|
||||
|
||||
from devlib.exception import TargetError
|
||||
from devlib.host import PACKAGE_BIN_DIRECTORY
|
||||
from devlib.platform import Platform
|
||||
from devlib.utils.ssh import AndroidGem5Connection, LinuxGem5Connection
|
||||
|
||||
class Gem5SimulationPlatform(Platform):
|
||||
|
||||
def __init__(self, name, host_output_dir, gem5_bin, gem5_args, gem5_virtio,
|
||||
gem5_telnet_port=None):
|
||||
|
||||
# First call the parent class
|
||||
super(Gem5SimulationPlatform, self).__init__(name=name)
|
||||
|
||||
# Start setting up the gem5 parameters/directories
|
||||
# The gem5 subprocess
|
||||
self.gem5 = None
|
||||
self.gem5_port = gem5_telnet_port or None
|
||||
self.stats_directory = host_output_dir
|
||||
self.gem5_out_dir = os.path.join(self.stats_directory, "gem5")
|
||||
self.gem5_interact_dir = '/tmp' # Host directory
|
||||
self.executable_dir = None # Device directory
|
||||
self.working_dir = None # Device directory
|
||||
self.stdout_file = None
|
||||
self.stderr_file = None
|
||||
self.stderr_filename = None
|
||||
if self.gem5_port is None:
|
||||
# Allows devlib to pick up already running simulations
|
||||
self.start_gem5_simulation = True
|
||||
else:
|
||||
self.start_gem5_simulation = False
|
||||
|
||||
# Find the first one that does not exist. Ensures that we do not re-use
|
||||
# the directory used by someone else.
|
||||
for i in xrange(sys.maxint):
|
||||
directory = os.path.join(self.gem5_interact_dir, "wa_{}".format(i))
|
||||
try:
|
||||
os.stat(directory)
|
||||
continue
|
||||
except OSError:
|
||||
break
|
||||
self.gem5_interact_dir = directory
|
||||
self.logger.debug("Using {} as the temporary directory."
|
||||
.format(self.gem5_interact_dir))
|
||||
|
||||
# Parameters passed onto gem5
|
||||
self.gem5args_binary = gem5_bin
|
||||
self.gem5args_args = gem5_args
|
||||
self.gem5args_virtio = gem5_virtio
|
||||
self._check_gem5_command()
|
||||
|
||||
# Start the interaction with gem5
|
||||
self._start_interaction_gem5()
|
||||
|
||||
def _check_gem5_command(self):
|
||||
"""
|
||||
Check if the command to start gem5 makes sense
|
||||
"""
|
||||
if self.gem5args_binary is None:
|
||||
raise TargetError('Please specify a gem5 binary.')
|
||||
if self.gem5args_args is None:
|
||||
raise TargetError('Please specify the arguments passed on to gem5.')
|
||||
self.gem5args_virtio = str(self.gem5args_virtio).format(self.gem5_interact_dir)
|
||||
if self.gem5args_virtio is None:
|
||||
raise TargetError('Please specify arguments needed for virtIO.')
|
||||
|
||||
def _start_interaction_gem5(self):
|
||||
"""
|
||||
Starts the interaction of devlib with gem5.
|
||||
"""
|
||||
|
||||
# First create the input and output directories for gem5
|
||||
if self.start_gem5_simulation:
|
||||
# Create the directory to send data to/from gem5 system
|
||||
self.logger.info("Creating temporary directory for interaction "
|
||||
" with gem5 via virtIO: {}"
|
||||
.format(self.gem5_interact_dir))
|
||||
os.mkdir(self.gem5_interact_dir)
|
||||
|
||||
# Create the directory for gem5 output (stats files etc)
|
||||
if not os.path.exists(self.stats_directory):
|
||||
os.mkdir(self.stats_directory)
|
||||
if os.path.exists(self.gem5_out_dir):
|
||||
raise TargetError("The gem5 stats directory {} already "
|
||||
"exists.".format(self.gem5_out_dir))
|
||||
else:
|
||||
os.mkdir(self.gem5_out_dir)
|
||||
|
||||
# We need to redirect the standard output and standard error for the
|
||||
# gem5 process to a file so that we can debug when things go wrong.
|
||||
f = os.path.join(self.gem5_out_dir, 'stdout')
|
||||
self.stdout_file = open(f, 'w')
|
||||
f = os.path.join(self.gem5_out_dir, 'stderr')
|
||||
self.stderr_file = open(f, 'w')
|
||||
# We need to keep this so we can check which port to use for the
|
||||
# telnet connection.
|
||||
self.stderr_filename = f
|
||||
|
||||
# Start gem5 simulation
|
||||
self.logger.info("Starting the gem5 simulator")
|
||||
|
||||
command_line = "{} --outdir={} {} {}".format(self.gem5args_binary,
|
||||
self.gem5_out_dir,
|
||||
self.gem5args_args,
|
||||
self.gem5args_virtio)
|
||||
self.logger.debug("gem5 command line: {}".format(command_line))
|
||||
self.gem5 = subprocess.Popen(command_line.split(),
|
||||
stdout=self.stdout_file,
|
||||
stderr=self.stderr_file)
|
||||
|
||||
else:
|
||||
# The simulation should already be running
|
||||
# Need to dig up the (1) gem5 simulation in question (2) its input
|
||||
# and output directories (3) virtio setting
|
||||
self._intercept_existing_gem5()
|
||||
|
||||
# As the gem5 simulation is running now or was already running
|
||||
# we now need to find out which telnet port it uses
|
||||
self._intercept_telnet_port()
|
||||
|
||||
def _intercept_existing_gem5(self):
|
||||
"""
|
||||
Intercept the information about a running gem5 simulation
|
||||
e.g. pid, input directory etc
|
||||
"""
|
||||
self.logger("This functionality is not yet implemented")
|
||||
raise TargetError()
|
||||
|
||||
def _intercept_telnet_port(self):
|
||||
"""
|
||||
Intercept the telnet port of a running gem5 simulation
|
||||
"""
|
||||
|
||||
if self.gem5 is None:
|
||||
raise TargetError('The platform has no gem5 simulation! '
|
||||
'Something went wrong')
|
||||
while self.gem5_port is None:
|
||||
# Check that gem5 is running!
|
||||
if self.gem5.poll():
|
||||
raise TargetError("The gem5 process has crashed with error code {}!".format(self.gem5.poll()))
|
||||
|
||||
# Open the stderr file
|
||||
with open(self.stderr_filename, 'r') as f:
|
||||
for line in f:
|
||||
m = re.search(r"Listening for system connection on port (?P<port>\d+)", line)
|
||||
if m:
|
||||
port = int(m.group('port'))
|
||||
if port >= 3456 and port < 5900:
|
||||
self.gem5_port = port
|
||||
break
|
||||
# Check if the sockets are not disabled
|
||||
m = re.search(r"Sockets disabled, not accepting terminal connections", line)
|
||||
if m:
|
||||
raise TargetError("The sockets have been disabled!"
|
||||
"Pass --listener-mode=on to gem5")
|
||||
else:
|
||||
time.sleep(1)
|
||||
|
||||
def init_target_connection(self, target):
|
||||
"""
|
||||
Update the type of connection in the target from here
|
||||
"""
|
||||
if target.os == 'linux':
|
||||
target.conn_cls = LinuxGem5Connection
|
||||
else:
|
||||
target.conn_cls = AndroidGem5Connection
|
||||
|
||||
def setup(self, target):
|
||||
"""
|
||||
Deploy m5 if not yet installed
|
||||
"""
|
||||
m5_path = target.get_installed('m5')
|
||||
if m5_path is None:
|
||||
m5_path = self._deploy_m5(target)
|
||||
target.conn.m5_path = m5_path
|
||||
|
||||
# Set the terminal settings for the connection to gem5
|
||||
self._resize_shell(target)
|
||||
|
||||
def update_from_target(self, target):
|
||||
"""
|
||||
Set the m5 path and if not yet installed, deploy m5
|
||||
Overwrite certain methods in the target that either can be done
|
||||
more efficiently by gem5 or don't exist in gem5
|
||||
"""
|
||||
m5_path = target.get_installed('m5')
|
||||
if m5_path is None:
|
||||
m5_path = self._deploy_m5(target)
|
||||
target.conn.m5_path = m5_path
|
||||
|
||||
# Overwrite the following methods (monkey-patching)
|
||||
self.logger.debug("Overwriting the 'capture_screen' method in target")
|
||||
# Housekeeping to prevent recursion
|
||||
setattr(target, 'target_impl_capture_screen', target.capture_screen)
|
||||
target.capture_screen = types.MethodType(_overwritten_capture_screen, target)
|
||||
self.logger.debug("Overwriting the 'reset' method in target")
|
||||
target.reset = types.MethodType(_overwritten_reset, target)
|
||||
self.logger.debug("Overwriting the 'reboot' method in target")
|
||||
target.reboot = types.MethodType(_overwritten_reboot, target)
|
||||
|
||||
# Call the general update_from_target implementation
|
||||
super(Gem5SimulationPlatform, self).update_from_target(target)
|
||||
|
||||
def gem5_capture_screen(self, filepath):
|
||||
file_list = os.listdir(self.gem5_out_dir)
|
||||
screen_caps = []
|
||||
for f in file_list:
|
||||
if '.bmp' in f:
|
||||
screen_caps.append(f)
|
||||
|
||||
successful_capture = False
|
||||
if len(screen_caps) == 1:
|
||||
# Bail out if we do not have image, and resort to the slower, built
|
||||
# in method.
|
||||
try:
|
||||
import Image
|
||||
gem5_image = os.path.join(self.gem5_out_dir, screen_caps[0])
|
||||
temp_image = os.path.join(self.gem5_out_dir, "file.png")
|
||||
im = Image.open(gem5_image)
|
||||
im.save(temp_image, "PNG")
|
||||
shutil.copy(temp_image, filepath)
|
||||
os.remove(temp_image)
|
||||
gem5_logger.info("capture_screen: using gem5 screencap")
|
||||
successful_capture = True
|
||||
|
||||
except (shutil.Error, ImportError, IOError):
|
||||
pass
|
||||
|
||||
return successful_capture
|
||||
|
||||
def _deploy_m5(self, target):
|
||||
# m5 is not yet installed so install it
|
||||
host_executable = os.path.join(PACKAGE_BIN_DIRECTORY,
|
||||
target.abi, 'm5')
|
||||
return target.install(host_executable)
|
||||
|
||||
def _resize_shell(self, target):
|
||||
"""
|
||||
Resize the shell to avoid line wrapping issues.
|
||||
|
||||
"""
|
||||
# Try and avoid line wrapping as much as possible.
|
||||
target.execute('{} stty columns 1024'.format(target.busybox))
|
||||
target.execute('reset', check_exit_code=False)
|
||||
|
||||
# Methods that will be monkey-patched onto the target
|
||||
def _overwritten_reset(self):
|
||||
raise TargetError('Resetting is not allowed on gem5 platforms!')
|
||||
|
||||
def _overwritten_reboot(self):
|
||||
raise TargetError('Rebooting is not allowed on gem5 platforms!')
|
||||
|
||||
def _overwritten_capture_screen(self, filepath):
|
||||
connection_screencapped = self.platform.gem5_capture_screen(filepath)
|
||||
if connection_screencapped == False:
|
||||
# The connection was not able to capture the screen so use the target
|
||||
# implementation
|
||||
self.logger.debug('{} was not able to screen cap, using the original target implementation'.format(self.platform.__class__.__name__))
|
||||
self.target_impl_capture_screen(filepath)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user