#!/usr/bin/python
# Copyright (c) 2016 Princeton University
# 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 Princeton University 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 PRINCETON UNIVERSITY "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 PRINCETON UNIVERSITY 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.

# Description: OpenPiton patch Synopsys RM script.
#              This script can also be used to generate patches
# Date Created: 3/28/2016
# Author: Michael McKeown

import sys, os, argparse, re, subprocess, shutil, datetime, stat

#
# Constants
#

# RM files and mapping to openpiton file
DC_RM_FILES     = {'rm_setup/common_setup.tcl': 'common/common_setup.tcl',
                   'rm_setup/dc_setup.tcl': 'common/dc_setup.tcl', 
                   'rm_setup/dc_setup_filenames.tcl': 'common/dc_setup_filenames.tcl',
                   'rm_dc_scripts/dc_top.tcl': 'syn/dc.tcl',
                   'rm_dc_scripts/fm_top.tcl': 'rvs/fm.tcl'}
PT_RM_FILES     = {'rm_setup/pt_setup.tcl': 'common/pt_setup.tcl',
                   'rm_pt_scripts/pt.tcl': 'sta/pt.tcl' }
ICC_RM_FILES    = {'rm_setup/icc_setup.tcl': 'common/icc_setup.tcl',
                   'rm_icc_scripts/clock_opt_cts_icc.tcl': 'par/clock_opt_cts_icc.tcl',
                   'rm_icc_scripts/common_cts_settings_icc.tcl': 'common/settings/common_cts_settings_icc.tcl',
                   'rm_icc_scripts/common_optimization_settings_icc.tcl': 
                        'common/settings/common_optimization_settings_icc.tcl',
                   'rm_icc_scripts/common_placement_settings_icc.tcl': 
                        'common/settings/common_placement_settings_icc.tcl',
                   'rm_icc_scripts/common_post_cts_timing_settings.tcl': 
                        'common/settings/common_post_cts_timing_settings.tcl',
                   'rm_icc_scripts/common_route_opt_tio_settings_icc.tcl':
                        'common/settings/common_route_opt_tio_settings_icc.tcl',
                   'rm_icc_scripts/init_design_icc.tcl': 'common/init_design_icc.tcl',
                   'rm_icc_scripts/place_opt_icc.tcl': 'par/place_opt_icc.tcl',
                   'rm_icc_zrt_scripts/chip_finish_icc.tcl': 'par/chip_finish_icc.tcl',
                   'rm_icc_zrt_scripts/clock_opt_psyn_icc.tcl': 'par/clock_opt_psyn_icc.tcl',
                   'rm_icc_zrt_scripts/clock_opt_route_icc.tcl': 'par/clock_opt_route_icc.tcl',
                   'rm_icc_zrt_scripts/common_route_si_settings_zrt_icc.tcl': 
                        'common/settings/common_route_si_settings_zrt_icc.tcl',
                   'rm_icc_zrt_scripts/eco_icc.tcl': 'eco/eco_icc.tcl',
                   'rm_icc_zrt_scripts/focal_opt_icc.tcl': 'par/focal_opt_icc.tcl',
                   'rm_icc_zrt_scripts/metal_fill_icc.tcl': 'par/metal_fill_icc.tcl',
                   'rm_icc_zrt_scripts/outputs_icc.tcl': 'common/outputs_icc.tcl',
                   'rm_icc_zrt_scripts/route_icc.tcl': 'par/route_icc.tcl',
                   'rm_icc_zrt_scripts/route_opt_icc.tcl': 'par/route_opt_icc.tcl',
                   'rm_icc_zrt_scripts/signoff_drc_icc.tcl': 'par/signoff_drc_icc.tcl'}

# Verify RM script directories exist and are readable
def dirpath(string):
    if not os.path.isdir(string):
        raise argparse.ArgumentTypeError('No such Synopsys RM script path: ' + string)
    if not os.access(string, os.R_OK):
        raise argparse.ArgumentTypeError('Synopsys RM script path not readable: ' + string)
    else :
        return string

# Parse the command line arguments
def parse_cmd_args():
    parser = argparse.ArgumentParser(description='OpenPiton generate back-end flow from Synopsys RM scripts and OpenPiton patch. This script can also be used to generate patches from the current OpenPiton back-end flow state.')
    parser.add_argument('-g', '--gen', action='store_true', 
                        help='Generate a patch from the current state of OpenPiton back-end scripts (default is to generate back-end flow from Synopsys RM scripts and OpenPiton patch)')
    parser.add_argument('-n', '--no_remove_patch_files', action='store_true',
                        help='Only applies when -g,--gen is specified.  Indicates to only generate patches and md5checksums. Will not remove the files to be generated from patching.  Useful when generating multiple patches from different Synopsys RM versions')
    parser.add_argument('-d', '--dc_rm_path', type=dirpath,
                        help='Path to directory containing extracted Synopsys Design Compiler RM scripts')
    parser.add_argument('-p', '--pt_rm_path', type=dirpath,
                        help='Path to directory containing extracted Synopsys Primetime RM scripts')
    parser.add_argument('-i', '--icc_rm_path', type=dirpath,
                        help='Path to directory containing extracted Synopsys IC Compiler RM scripts')
    args = parser.parse_args(sys.argv[1:])
    
    # Make sure at least one RM path was provided
    if not (args.dc_rm_path or args.pt_rm_path or args.icc_rm_path):
        parser.error('No Synopsys RM scripts specified, specify at least one of DC_RM_PATH, PT_RM_PATH, ICC_RM_PATH')

    # Set program
    args.prog = os.path.basename(sys.argv[0])

    return args

# Gets the version of the Synopsys RM scripts
# given a release notes file
def get_rm_version(prog, rel_notes):
    fp = open(rel_notes, 'r')
    version = None
    for line in fp:
        # Look for version string
        match = re.match('# Version: (\S+) \(\S+ \d+, \d+\)', line)
        if match:
            version = match.group(1)
            break 
    fp.close()

    if not version :
        print prog + ": Could not find Synopsys RM version in release notes (" + rel_notes + ")."
    return version

# Removes comments and empty lines from settings file
def preprocess_settings_file(settings_file):
    settings_file_preprocessed = settings_file + ".preprocess"
    fp_orig = open(settings_file, 'r')
    fp_tmp = open(settings_file_preprocessed, 'w')
    for line in fp_orig:
        match = re.match("(^\s*#)|(^\s*$)", line)
        if not match:
            fp_tmp.write(line)
    fp_tmp.close()
    fp_orig.close()

    os.remove(settings_file)
    shutil.move(settings_file_preprocessed, settings_file)

def file_md5sum(prog, file):
    md5sum_cmd = ["md5sum", file]
    p = subprocess.Popen(md5sum_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (out, err) = p.communicate()

    if err != "" or p.returncode :
        print prog + ": Error generating checksum of '" + file + "'."
        return None

    return out.split()[0]

# Generates a md5 checksum from the
# current working directory or optionally
# a list of files in the current working directory
# provided through the files argument
def gen_md5sum(prog, md5file, files=None):
    find_cmd = ['find', '.', '-type', 'f', 
                '!', '-name', 'README.*.txt', 
                '!', '-name', '*-RMsettings.txt',
                '!', '-name', 'Release_Notes.*.txt',
                '-print0']
    if files == None:
        # Need to use find and pipe files to md5sum
        md5sum_cmd = ['xargs', '-0', 'md5sum']
    else:
        # Just call md5sum with provided files list
        md5sum_cmd = ['md5sum']
        md5sum_cmd.extend(files)

    fp = open(md5file, 'w')
    if files == None:
        # Run find piping to md5sum  and pipe output to a file
        p = subprocess.Popen(find_cmd, stdout=subprocess.PIPE)
        p2 = subprocess.Popen(md5sum_cmd, stdin=p.stdout, stdout=fp)
    else:
        # Just run md5sum and pipe output to a file
        p2 = subprocess.Popen(md5sum_cmd, stdout=fp)
    p2.wait()
    fp.close()

    if files == None:
        failed = (p.returncode > 0) or (p2.returncode > 0)
    else:
        failed = p2.returncode

    if failed :
        print prog + ": Error generating integrity checksum '" + md5file + "'."
        return False

    return True

# Generates md5 checksums for Synopsys RM
def gen_rm_integrity_checks(prog, dv_root, dc, pt, icc):
    print prog + ": Generating integrity checks for Synopsys RM scripts..."
    retval = True

    for rm, rm_id in zip([dc, pt, icc], ["DC", "PT", "ICC"]):
        if rm:
            # Get release notes file for version
            rel_notes = os.path.join(rm, 'Release_Notes.' + rm_id + '-RM.txt')
            if not os.path.isfile(rel_notes):
                print prog + ": Unable to locate Synopsys RM release notes (" + rel_notes + ")."
                retval = False 
            elif not os.access(rel_notes, os.R_OK):
                print prog + ": Unable to access Synopsys RM release notes (" + rel_notes + ")."
                retval = False
            else:
                # Get version from release notes
                version = get_rm_version(prog, rel_notes)
                if not version:
                    retval = False
                else :
                    # Get settings file
                    settings = os.path.join(rm, rm_id + '-RMsettings.txt')
                    if not os.path.isfile(settings):
                        print prog + ": Unable to locate Synopsys RM settings file (" + settings + ")."
                        retval = False
                    elif not os.access(settings, os.R_OK):
                        print prog + ": Unable to access Synopsys RM settings file (" + settings + ")."
                        retval = False
                    else:
                        # Copy settings to temporary file and remove comments and empty lines
                        # The reason for this is Synopsys seems to change the year in the
                        # comments and sometimes arbitrarily adds white space?  Should
                        # make this process more robust.  Other files do not seem to be
                        # affected
                        settings_tmp = os.path.join(dv_root, "tools/synopsys/patch/." + rm_id + "-RMsettings.txt.tmp")
                        shutil.copyfile(settings, settings_tmp)
                        preprocess_settings_file(settings_tmp)

                        settings_md5 = file_md5sum(prog, settings_tmp)

                        os.remove(settings_tmp)

                        if settings_md5 == None :
                            print prog + ": Failed to take checksum of settings file '" + settings + "'."
                            retval = False
                        else :
                            # Save directory and go to RM scripts dir
                            cwd = os.getcwd()
                            os.chdir(rm)

                            md5file = os.path.join(dv_root, 'tools/synopsys/patch/' + rm_id + '/' + version + '-' + settings_md5 + '.md5sum')

                            if not gen_md5sum(prog, md5file) :
                                print prog + ": Integrity checksum generation failed for '" + rm + "'."
                                retval = False          

                            # Restore directory
                            os.chdir(cwd)

    if retval:
        print prog + ": Synopsys RM integrity checks sucessfully generated."
        return True
    else:
        print prog + ": Failed to generate Synopsys RM integrity checks."
        return False

# Checks the current directory against
# a md5 checksum file
def check_md5sum(prog, md5file):
    # Run md5sum command
    md5sum_cmd = ['md5sum', '-c', md5file]
    p = subprocess.Popen(md5sum_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (out, err) = p.communicate()
    
    # Check output
    retval = True
    if err != "" or p.returncode:
        print prog + ": Integrity checking '" + md5file + "' failed."
        retval = False
    for line in out.split('\n'):
        if line != "":
            (file_path, status) = line.split(": ")
            if status != "OK" :
                print prog + ": Integrity check of file '" + file_path + "' from '" + md5file + "' failed."
                retval = False

    return retval

# Checks the integrity of the provided
# Synopsys RM scripts
def check_rm_integrity(prog, dv_root, dc, pt, icc):
    print prog + ": Checking integrity of Synopsys RM..."
    retval = True

    for rm, rm_id in zip([dc, pt, icc], ["DC", "PT", "ICC"]):
        if rm:
            # Get release notes file for version
            rel_notes = os.path.join(rm, 'Release_Notes.' + rm_id + '-RM.txt')
            if not os.path.isfile(rel_notes):
                print prog + ": Unable to located Synopsys RM release notes (" + rel_notes + ")."
                retval = False 
            elif not os.access(rel_notes, os.R_OK):
                print prog + ": Unable to access Synopsys RM release notes (" + rel_notes + ")."
                retval = False
            else:
                # Get version from release notes
                version = get_rm_version(prog, rel_notes)
                if not version:
                    retval = False
                else :
                    # Get settings file
                    settings = os.path.join(rm, rm_id + '-RMsettings.txt')
                    if not os.path.isfile(settings):
                        print prog + ": Unable to locate Synopsys RM settings file (" + settings + ")."
                        retval = False
                    elif not os.access(settings, os.R_OK):
                        print prog + ": Unable to access Synopsys RM settings file (" + settings + ")."
                        retval = False
                    else:
                        # Copy settings to temporary file and remove comments and empty lines
                        # The reason for this is Synopsys seems to change the year in the
                        # comments and sometimes arbitrarily adds white space?  Should
                        # make this process more robust.  Other files do not seem to be
                        # affected
                        settings_tmp = os.path.join(dv_root, "tools/synopsys/patch/." + rm_id + "-RMsettings.txt.tmp")
                        shutil.copyfile(settings, settings_tmp)
                        preprocess_settings_file(settings_tmp) 

                        settings_md5 = file_md5sum(prog, settings_tmp)

                        os.remove(settings_tmp)

                        if settings_md5 == None :
                            print prog + ": Failed to take checksum of settings file '" + settings + "'."
                            retval = False
                        else :
                            # Save directory and go to RM scripts dir
                            cwd = os.getcwd()
                            os.chdir(rm)

                            md5file = os.path.join(dv_root, 'tools/synopsys/patch/' + rm_id + '/' + version + '-' + settings_md5 + '.md5sum')
                            if not os.path.isfile(md5file):
                                print prog + ": Valid integrity checksum not found for '" + rm + "' with " + rm_id + "-RM version '" + version +"'."
                                retval = False
                            elif not os.access(md5file, os.R_OK):
                                print prog + ": Integrity checksum for '" + rm + "' with " + rm_id + "-RM version '" + version + "' is not accessible."
                                retval = False

                            if retval and not check_md5sum(prog, md5file) :
                                print prog + ": Integrity check of '" + rm + "' failed."
                                retval = False

                            # Restore directory
                            os.chdir(cwd)
                
    if retval:
        print prog + ": Synopsys RM integrity check passed."
        return True
    else:
        print prog + ": Synopsys RM integrity check failed.  Please see the OpenPiton documentation for supported Synpsys RM versions and options."
        return False


# Clears any whitespace in any files in the current working directory
def clear_whitespace_cwd(prog):
    for root, dirnames, filenames in os.walk(os.getcwd()):
        for filename in filenames :
            filepath = os.path.join(root, filename)
            filepath_tmp = filepath + '.tmp'
            oldfp = open(filepath, 'r')
            newfp = open(filepath_tmp, 'w')
            for line in oldfp:
                # Need to take care of space between comment start '#'
                # and no space between it
                match = re.match("(^\s*\#+)\s+(.+)", line)
                if match :
                    line = match.group(1) + match.group(2)
                newfp.write(' '.join(line.strip().split()))
            newfp.close()
            oldfp.close()

            os.remove(filepath)
            shutil.move(filepath_tmp, filepath)

# Generates checksum for checking OpenPiton integrity
def gen_openpiton_integrity_checks(prog, dv_root, dc, pt, icc):
    print prog + ": Generating integrity checks for OpenPiton flow..."
    retval = True

    openpiton_flow = os.path.join(dv_root, 'tools/synopsys/script/')

    # Setup temporary directory
    tmp_dir = os.path.join(dv_root, 'tools/synopsys/.synrm_patch_tmp/')
    if os.path.exists(tmp_dir):
        print prog + ": Warning synrm_patch temporary working directory already exists...removing and overwritting"
        shutil.rmtree(tmp_dir)

    # Copy all of OpenPiton flow to temporary directory
    shutil.copytree(openpiton_flow, tmp_dir)

    # Save directory and go to OpenPiton scripts dir
    cwd = os.getcwd()
    os.chdir(tmp_dir)

    # Clear whitespace differences we don't care about
    clear_whitespace_cwd(prog)

    for rm, rm_id, rm_files in zip([dc, pt, icc], ["DC", "PT", "ICC"], [DC_RM_FILES, PT_RM_FILES, ICC_RM_FILES]):
        if rm:
            md5file = os.path.join(dv_root, 'tools/synopsys/patch/' + rm_id + '/OpenPiton.md5sum')
            openpiton_files = [rm_files[k] for k in rm_files]

            if not gen_md5sum(prog, md5file, openpiton_files) :
                print prog + ": OpenPiton integrity checksum generation failed for " + rm_id + "."
                retval = False 

    # Restore directory
    os.chdir(cwd)

    # Remove temporary directory
    shutil.rmtree(tmp_dir)

    if retval:
        print prog + ": OpenPiton integrity checks successfully generated."
        return True
    else:
        print prog + ": Failed to generate OpenPiton integrity checks."
        return False

# Creates subdirectories for a relative path
# from a absolute path
def create_subdirs(root_dir, rel_path):
    rel_path_parts = rel_path.split('/')
    if len(rel_path_parts) > 1:
        rel_path_abs = root_dir
        for subdir in rel_path_parts[:-1]:
            rel_path_abs = os.path.join(rel_path_abs, subdir)
            if not os.path.isdir(rel_path_abs):
                os.mkdir(rel_path_abs)

# Copies Synopsys RM files to the specified directory
def copy_rm_files(prog, cp_dir, rm_path, files):
    print prog + ": Copying Synopsys RM files from '" + rm_path + "' to '" + cp_dir + "'..."
    retval = True

    for rm_file, openpiton_file in files.iteritems() :
        rm_file_path = os.path.join(rm_path, rm_file)
        openpiton_file_path = os.path.join(cp_dir, openpiton_file)

        # Check RM file exists
        if not os.path.isfile(rm_file_path):
            print prog + ": Unable to locate Synopsys RM file '" + rm_file_path + "'."
            retval = False
        if not os.access(rm_file_path, os.R_OK):
            print prog + ": Unable to access Synopsys RM file '" + rm_file_path + "'."
            retval = False
        else :
            # Check subdirectories of openpiton file exists and if not create them
            create_subdirs(cp_dir, openpiton_file)

            # Copy the file
            shutil.copyfile(rm_file_path, openpiton_file_path)

    if retval:
        print prog + ": Successfully copied Synopsys RM files from '" + rm_path + "'."
        return True
    else:
        print prog + ": Failed to copy Synopsys RM files from '" + rm_path + "'."
        return False

# Post processes the patch file.  Needs some fixing to work with patch using ed scripts
def post_process_patch(prog, patch_file):
    print prog + ": Post-processing patch file '" + patch_file + "'."
    retval = True

    # Need to insert file headers for the ed script to work
    # with patch
    patch_file_tmp = patch_file + ".tmp"
    origfp = open(patch_file, 'r')
    newfp = open(patch_file_tmp, 'w')
    for line in origfp:
        match = re.match('diff -\S+ (.*) (.*)', line)
        if match:
            newfp.write(line)
            newfp.write('--- ' + match.group(1) + ' ' + str(datetime.datetime.now()) + ' -0400\n')
            newfp.write('+++ ' + match.group(2) + ' ' + str(datetime.datetime.now()) + ' -0400\n')
        else:
            newfp.write(line)
    newfp.close()
    origfp.close()

    os.remove(patch_file)
    shutil.move(patch_file_tmp, patch_file)

    if retval:
        print prog + ": Successfully post-processed patch file '" + patch_file + "'."
        return True
    else:
        print prog + ": Failed post-processing file '" + pach_file + "'."

# Diffs OpenPiton against Synopsys RM scripts to produce patch
def diff_openpiton(prog, dv_root, dc, pt, icc):
    retval = True
    for rm, rm_id, rm_files in zip([dc, pt, icc], ["DC", "PT", "ICC"], [DC_RM_FILES, PT_RM_FILES, ICC_RM_FILES]):
        if rm:
            localretval = True

            # Make temporary directory to diff OpenPiton flow against (after copying RM files)
            diff_dir = os.path.join(dv_root, 'tools/synopsys/.synrm_patch_tmp/')
            if os.path.exists(diff_dir):
                print prog + ": Warning synrm_patch temporary working directory already exists...removing and overwritting"
                shutil.rmtree(diff_dir)
            os.mkdir(diff_dir)

            # Copy RM files to diff directory and rename for cleaner diffs
            if not copy_rm_files(prog, diff_dir, rm, rm_files):
                localretval = False
            else:
                print prog + ": Generating OpenPiton patch for '" + rm + "'...."
                
                # Get release notes file for version
                rel_notes = os.path.join(rm, 'Release_Notes.' + rm_id + '-RM.txt')
                if not os.path.isfile(rel_notes):
                    print prog + ": Unable to located Synopsys RM release notes (" + rel_notes + ")."
                    localretval = False
                elif not os.access(rel_notes, os.R_OK):
                    print prog + ": Unable to access Synopsys RM release notes (" + rel_notes + ")."
                    localretval = False
                else:
                    # Get version from release notes
                    version = get_rm_version(prog, rel_notes)
                    if not version:
                        localretval = False
                    else :
                        # Get settings file
                        settings = os.path.join(rm, rm_id + '-RMsettings.txt')
                        if not os.path.isfile(settings):
                            print prog + ": Unable to locate Synopsys RM settings file (" + settings + ")."
                            retval = False
                        elif not os.access(settings, os.R_OK):
                            print prog + ": Unable to access Synopsys RM settings file (" + settings + ")."
                            retval = False
                        else:
                            # Copy settings to temporary file and remove comments and empty lines
                            # The reason for this is Synopsys seems to change the year in the
                            # comments and sometimes arbitrarily adds white space?  Should
                            # make this process more robust.  Other files do not seem to be
                            # affected
                            settings_tmp = os.path.join(dv_root, "tools/synopsys/patch/." + rm_id + "-RMsettings.txt.tmp")
                            shutil.copyfile(settings, settings_tmp)
                            preprocess_settings_file(settings_tmp)

                            settings_md5 = file_md5sum(prog, settings_tmp) 

                            os.remove(settings_tmp)

                            if settings_md5 == None :
                                print prog + ": Failed to take checksum of settings file '" + settings + "'."
                                retval = False
                            else : 
                                # Some file paths
                                openpiton_flow = os.path.join(dv_root, 'tools/synopsys/script/')
                                patch_file = os.path.join(dv_root, 'tools/synopsys/patch/' + rm_id + '/' + version + '-' + settings_md5 + '.diff')
                                if os.path.isfile(patch_file):
                                    print prog + ": Warning patch file '" + patch_file +  "' exists...overwriting."

                                cwd = os.getcwd()
                                os.chdir(os.path.join(dv_root, 'tools/synopsys'))

                                diff_dir_rel = os.path.relpath(diff_dir)
                                openpiton_flow_rel = os.path.relpath(openpiton_flow)

                                # Run diff to generate patch
                                diff_cmd = ['diff', '-erw', diff_dir_rel, openpiton_flow_rel]
                                fp = open(patch_file, 'w')
                                p = subprocess.Popen(diff_cmd, stdout=fp, stderr=subprocess.PIPE)
                                (out, err) = p.communicate()
                                fp.close()
            
                                if err != "" or p.returncode > 1:
                                    print prog + ": Error generating patch for '" + rm + "'."
                                    localretval = False
                                else:
                                    if not post_process_patch(prog, patch_file):
                                        print prog + ": Failed to post-process patch file '" + patch_file + "'."
                                        localretval = False

                                os.chdir(cwd)

                if localretval :
                    print prog + ": Successfully generated OpenPiton patch for '" + rm + "'."
                else:
                    print prog + ": Failed to generate OpenPiton patch for '" + rm + "'."
                    retval = False

            # Clean up temporary directory regardless
            print prog + ": Cleaning up temporary directory."
            shutil.rmtree(diff_dir)

    return retval

def gen_tarball(prog, dv_root, dc, pt, icc) :
    localretval = True

    # Some file paths
    patch_path = os.path.join(dv_root, 'tools/synopsys/patch/')
    if not os.path.isdir(patch_path):
        print prog + ": patch subdirectory '" + patch_path +  "' does not exist. Cannot create patch tarball."
        return False
    
    cwd = os.getcwd()
    os.chdir(os.path.join(dv_root, 'tools/synopsys'))

    tar_cmd = 'tar czf patch.tar.gz patch/'

    returncode = os.system(tar_cmd)

    if returncode:
        print prog + ": Error generating patch tarball for '" + rm + "'."
        os.chdir(cwd)
        return False

    rm_cmd = 'rm -rf patch/'

    returncode = os.system(rm_cmd)

    if returncode:
        print prog + ": Error removing patch/ directory."
        localretval = False

    os.chdir(cwd)

    return localretval

def extract_tarball(prog, dv_root, dc, pt, icc) :
    localretval = True

    # Some file paths
    patch_path = os.path.join(dv_root, 'tools/synopsys/patch.tar.gz')
    if not os.path.isfile(patch_path):
        patch_dir = os.path.join(dv_root, 'tools/synopsys/patch/')
        if not os.path.isdir(patch_dir):
            print prog + ": patch tarball '" + patch_path +  "' does not exist and was not already extracted. Cannot extract patch tarball."
            return False
        else:
            print prog + ": patch tarball '" + patch_path +  "' does not exist. Patch directory already exists so assuming tarball was already extracted."
            return True
    
    cwd = os.getcwd()
    os.chdir(os.path.join(dv_root, 'tools/synopsys'))

    tar_cmd = 'tar xzf patch.tar.gz'

    returncode = os.system(tar_cmd)

    if returncode:
        print prog + ": Error extracting patch tarball for '" + rm + "'."
        localretval = False

    os.chdir(cwd)

    return localretval

def clean_openpiton_patch_files(prog, dv_root, dc, pt, icc) :
    print prog + ": Cleaning OpenPiton files that will be patched...."
    for rm, rm_id, rm_files in zip([dc, pt, icc], ["DC", "PT", "ICC"], [DC_RM_FILES, PT_RM_FILES, ICC_RM_FILES]):
        if rm:
            for rm_file, openpiton_file in rm_files.iteritems():
                openpiton_file_path = os.path.join(dv_root, 'tools/synopsys/script/') 
                openpiton_file_path = os.path.join(openpiton_file_path, openpiton_file)
                if os.path.isfile(openpiton_file_path):
                    os.remove(openpiton_file_path)

    print prog + ": Finished cleaning patched OpenPiton files."
    return True

# Generates patch for Synopsys RM to OpenPiton
def gen_patch(dv_root, args):
    retval = True

    # Check the integrity of the Synopsys RM
    if not gen_rm_integrity_checks(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    # Generate OpenPiton integrity checksum
    if retval and not gen_openpiton_integrity_checks(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    # Generate diff of Synopsys RM and OpenPiton flow
    if retval and not diff_openpiton(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    if not args.no_remove_patch_files:
        if retval and not gen_tarball(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
            retval = False

        if retval and not clean_openpiton_patch_files(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path) :
            retval = False

    if retval:
        print args.prog + ": Successfully generated Synopsys RM patch."
        return True
    else:
        print args.prog + ": Failed to generate Synopsys RM patch."
        return False

# Path Synopsys RM to OpenPiton
def patch_openpiton(prog, dv_root, dc, pt, icc):
    retval = True
    for rm, rm_id, rm_files in zip([dc, pt, icc], ["DC", "PT", "ICC"], [DC_RM_FILES, PT_RM_FILES, ICC_RM_FILES]):
        if rm:
            localretval = True
            # Copy files to OpenPiton flow directory
            openpiton_flow = os.path.join(dv_root, 'tools/synopsys/script/')
            if not copy_rm_files(prog, openpiton_flow, rm, rm_files):
                localretval = False
            else:
                print prog + ": Patching Synopsys RM '" + rm + "' to OpenPiton..."
                
                # Get release notes file for version
                rel_notes = os.path.join(rm, 'Release_Notes.' + rm_id + '-RM.txt')
                if not os.path.isfile(rel_notes):
                    print prog + ": Unable to located Synopsys RM release notes (" + rel_notes + ")."
                    localretval = False
                elif not os.access(rel_notes, os.R_OK):
                    print prog + ": Unable to access Synopsys RM release notes (" + rel_notes + ")."
                    localretval = False
                else:
                    # Get version from release notes
                    version = get_rm_version(prog, rel_notes)
                    if not version:
                        localretval = False
                    else :
                        # Get settings file
                        settings = os.path.join(rm, rm_id + '-RMsettings.txt')
                        if not os.path.isfile(settings):
                            print prog + ": Unable to locate Synopsys RM settings file (" + settings + ")."
                            retval = False
                        elif not os.access(settings, os.R_OK):
                            print prog + ": Unable to access Synopsys RM settings file (" + settings + ")."
                            retval = False
                        else:
                            # Copy settings to temporary file and remove comments and empty lines
                            # The reason for this is Synopsys seems to change the year in the
                            # comments and sometimes arbitrarily adds white space?  Should
                            # make this process more robust.  Other files do not seem to be
                            # affected
                            settings_tmp = os.path.join(dv_root, "tools/synopsys/patch/." + rm_id + "-RMsettings.txt.tmp")
                            shutil.copyfile(settings, settings_tmp)
                            preprocess_settings_file(settings_tmp)

                            settings_md5 = file_md5sum(prog, settings_tmp)

                            os.remove(settings_tmp)

                            if settings_md5 == None :
                                print prog + ": Failed to take checksum of settings file '" + settings + "'."
                                retval = False
                            else :
                                # Patch file
                                patch_file = os.path.join(dv_root, 'tools/synopsys/patch/' + rm_id + '/' + version + '-' + settings_md5 + '.diff')
                                if not os.path.isfile(patch_file):
                                    print prog + ": Valid integrity checksum not found for '" + rm + "' with " + rm_id + "-RM version '" + version +"'."
                                    localretval = False
                                elif not os.access(patch_file, os.R_OK):
                                    print prog + ": Integrity checksum for '" + rm + "' with " + rm_id + "-RM version '" + version +"' is not accessible."
                                else :
                                    # Patch to OpenPiton flow
                                    patch_cmd = ["patch", "-efts", "-p1", "-d", openpiton_flow, "-i", patch_file]
                                    p = subprocess.Popen(patch_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                                    (out, err) = p.communicate()

                                    if out != "" or err != "" or p.returncode:
                                        print prog + ": Error patching '" + rm + "'."
                                        localretval = False

                if localretval:
                    print prog + ": Successfully patched Synopsys RM '" + rm + "' to OpenPiton."
                else :
                    print prog + ": Failed to patch Synopsys RM '" + rm + "' to OpenPiton."
                    retval = True

    return retval

# Check the integrity of the OpenPiton flow
def check_openpiton_integrity(prog, dv_root, dc, pt, icc):
    print prog + ": Checking integrity of OpenPiton flow..."
    retval = True

    openpiton_flow = os.path.join(dv_root, 'tools/synopsys/script')

    # Setup temporary directory
    tmp_dir = os.path.join(dv_root, 'tools/synopsys/.synrm_patch_tmp/')
    if os.path.exists(tmp_dir):
        print prog + ": Warning synrm_patch temporary working directory already exists...removing and overwritting"
        shutil.rmtree(tmp_dir)

    # Copy all of OpenPiton flow to temporary directory
    shutil.copytree(openpiton_flow, tmp_dir) 

    # Save directory and go to OpenPiton scripts dir
    cwd = os.getcwd()
    os.chdir(tmp_dir)

    # Clear whitespace differences we don't care about
    clear_whitespace_cwd(prog)

    for rm, rm_id in zip([dc, pt, icc], ["DC", "PT", "ICC"]):
        if rm:
            md5file = os.path.join(dv_root, 'tools/synopsys/patch/' + rm_id + '/OpenPiton.md5sum')

            if not check_md5sum(prog, md5file) :
                print prog + ": OpenPiton integrity check failed for " + rm_id + "."
                retval = False

    # Restore directory
    os.chdir(cwd)

    # Remove temporary directory
    shutil.rmtree(tmp_dir)

    if retval:
        print prog + ": OpenPiton integrity checks passed."
        return True
    else:
        print prog + ": OpenPiton integrity checks failed."
        return False 

# Patches Synopsys RM to OpenPiton
def patch(dv_root, args):
    retval = True

    # Untar the patch tarball
    if retval and not extract_tarball(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    # Check the integrity of the Synopsys RM
    if not check_rm_integrity(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    # Patch the Synopsys RM to OpenPiton flow
    if retval and not patch_openpiton(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    if retval and not check_openpiton_integrity(args.prog, dv_root, args.dc_rm_path, args.pt_rm_path, args.icc_rm_path):
        retval = False

    if retval:
        print args.prog + ": Successfully patched Synopsys RM to OpenPiton flow."
        return True
    else:
        print args.prog + ": Failed to patch Synopsys RM to OpenPiton flow."
        return False

def main():
    args = parse_cmd_args()

    dv_root = os.environ.get('DV_ROOT')
    if not dv_root:
        print args.prog + ": OpenPiton DV_ROOT environment variable not set"
        sys.exit(1)

    retval = False
    if args.gen:
        print args.prog + ": ########################################"
        print args.prog + ": # OpenPiton Generate Synopsys RM Patch #"
        print args.prog + ": ########################################"
        retval = gen_patch(dv_root, args)
    else :
        print args.prog + ": ####################################"
        print args.prog + ": # OpenPiton Patch Synopsys RM Flow #"
        print args.prog + ": ####################################"
        retval = patch(dv_root, args)

    if retval:
        # Success
        sys.exit(0)
    else:
        # Fail
        sys.exit(1)

if __name__ == "__main__" :
    main()
