mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-11-03 23:41:21 +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:
		@@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user