| # SPDX-FileCopyrightText: 2020 Efabless Corporation |
| # |
| # 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. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import argparse |
| import datetime |
| import logging |
| import subprocess |
| import sys |
| from pathlib import Path |
| |
| import precheck_logger |
| from check_manager import get_check_manager, open_source_checks, private_checks |
| from checks.utils.utils import file_hash, get_project_config, uncompress_gds |
| |
| |
| def log_tools_info(pdk_root, tools_info_path, pdks_info_path): |
| with open(tools_info_path, 'w') as tools_info: |
| klayout_version = subprocess.check_output(['klayout', '-v'], encoding='utf-8').replace('KLayout', '').lstrip().rstrip() |
| magic_version = subprocess.check_output(['magic', '--version'], encoding='utf-8').rstrip() |
| tools_info.write(f"KLayout: {klayout_version}\n") |
| tools_info.write(f"Magic: {magic_version}") |
| logging.info(f"{{{{Tools Info}}}} KLayout: v{klayout_version} | Magic: v{magic_version}") |
| with open(pdks_info_path, 'w') as pdks_info: |
| try: |
| pdk_dir = f"{pdk_root}/%s" |
| open_pdks_v_cmd = ['git', '-C', pdk_dir % 'open_pdks', 'rev-parse', '--verify', 'HEAD'] |
| skywater_pdk_v_cmd = ['git', '-C', pdk_dir % 'skywater-pdk', 'rev-parse', '--verify', 'HEAD'] |
| open_pdks_version = subprocess.check_output(open_pdks_v_cmd, encoding='utf-8').rstrip() |
| skywater_pdk_version = subprocess.check_output(skywater_pdk_v_cmd, encoding='utf-8').rstrip() |
| pdks_info.write(f"Open PDKs {open_pdks_version}\n") |
| pdks_info.write(f"Skywater PDK {skywater_pdk_version}") |
| logging.info(f"{{{{PDKs Info}}}} Open PDKs: {open_pdks_version} | Skywater PDK: {skywater_pdk_version}") |
| except Exception as e: |
| logging.error(f"MPW Precheck failed to get Open PDKs & Skywater PDK versions: {e}") |
| |
| |
| def run_precheck_sequence(precheck_config, project_config): |
| results = {} |
| logging.info(f"{{{{START}}}} Precheck Started, the full log '{precheck_config['log_path'].name}' will be located in '{precheck_config['log_path'].parent}'") |
| logging.info(f"{{{{PRECHECK SEQUENCE}}}} Precheck will run the following checks: {' '.join([get_check_manager(x, precheck_config, project_config).__surname__ for x in precheck_config['sequence']])}") |
| for check_count, entry in enumerate(precheck_config['sequence'], start=1): |
| check = get_check_manager(entry, precheck_config, project_config) |
| if check: |
| logging.info(f"{{{{STEP UPDATE}}}} Executing Check {check_count} of {len(precheck_config['sequence'])}: {check.__surname__}") |
| results[check.__surname__] = check.run() |
| |
| logging.info(f"{{{{FINISH}}}} Executing Finished, the full log '{precheck_config['log_path'].name}' can be found in '{precheck_config['log_path'].parent}'") |
| if False not in list(results.values()): |
| logging.info("{{SUCCESS}} All Checks Passed !!!") |
| else: |
| failed_checks = [x for x in results.keys() if results[x] is False] |
| logging.fatal(f"{{{{FAILURE}}}} {len(failed_checks)} Check(s) Failed: {failed_checks} !!!") |
| sys.exit(2) |
| |
| |
| def main(*args, **kwargs): |
| check_managers = private_checks if kwargs['private'] else open_source_checks |
| precheck_config = dict(input_directory=Path(kwargs['input_directory']), |
| output_directory=Path(kwargs['output_directory']), |
| caravel_root=Path(kwargs['caravel_root']), |
| pdk_root=Path(kwargs['pdk_root']), |
| private=kwargs['private'], |
| sequence=kwargs['sequence'], |
| log_path=Path(kwargs['log_path']), |
| default_content=Path(kwargs['default_content']), |
| check_managers=check_managers) |
| |
| uncompress_gds(precheck_config['input_directory']) |
| project_config = get_project_config(precheck_config['input_directory']) |
| |
| gds_info_path = precheck_config['log_path'].parent / 'gds.info' |
| with open(gds_info_path, 'w') as gds_info: |
| user_module_hash = file_hash(f"{precheck_config['input_directory']}/gds/{project_config['user_module']}.gds") |
| gds_info.write(f"{project_config['user_module']}.gds: {user_module_hash}") |
| logging.info(f"{{{{Project GDS Info}}}} {project_config['user_module']}: {user_module_hash}") |
| |
| tools_info_path = precheck_config['log_path'].parent / 'tools.info' |
| pdks_info_path = precheck_config['log_path'].parent / 'pdks.info' |
| log_tools_info(precheck_config['pdk_root'], tools_info_path, pdks_info_path) |
| |
| gds_file_path = precheck_config['input_directory'] / f"gds/{project_config['user_module']}.gds" |
| compressed_gds_file_path = precheck_config['input_directory'] / f"gds/{project_config['user_module']}.gds.gz" |
| if gds_file_path.exists() and compressed_gds_file_path.exists(): |
| logging.fatal("{{GDS VIOLATION}} Both a compressed and an uncompressed version the gds exist, ensure only one design file exists.") |
| sys.exit(255) |
| |
| run_precheck_sequence(precheck_config=precheck_config, project_config=project_config) |
| |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser(description="Runs the precheck tool.") |
| parser.add_argument('--input_directory', '-i', required=True, help="INPUT_DIRECTORY Absolute Path to the project.") |
| parser.add_argument('--caravel_root', '-cr', required=True, help="CARAVEL_ROOT Absolute Path to caravel.") |
| parser.add_argument('--pdk_root', '-p', required=True, help="PDK_ROOT, points to pdk installation path") |
| parser.add_argument('--output_directory', '-o', required=False, help="Output Directory, default=<input_directory>/precheck_results/DD_MMM_YYYY___HH_MM_SS.") |
| parser.add_argument('--private', action='store_true', help=f"If provided, precheck skips {open_source_checks.keys() - private_checks.keys()} checks that qualify the project to be Open Source") |
| parser.add_argument('checks', metavar='check', type=str, nargs='*', choices=list(open_source_checks.keys()).append([]), help=f"Checks to be run by the precheck: {' '.join(open_source_checks.keys())}") |
| args = parser.parse_args() |
| |
| # NOTE Separated to allow the option later on for a run tag |
| tag = f"{datetime.datetime.utcnow():%d_%b_%Y___%H_%M_%S}".upper() |
| output_directory = args.output_directory if args.output_directory else f"{args.input_directory}/precheck_results/{tag}" |
| Path(f"{output_directory}/logs").mkdir(parents=True, exist_ok=True) |
| Path(f"{output_directory}/outputs").mkdir(parents=True, exist_ok=True) |
| Path(f"{output_directory}/outputs/reports").mkdir(parents=True, exist_ok=True) |
| log_path = Path(output_directory) / 'logs/precheck.log' |
| precheck_logger.initialize_root_logger(log_path) |
| |
| if args.checks: |
| sequence = args.checks |
| else: |
| sequence = [x for x in private_checks.keys()] if args.private else [x for x in open_source_checks.keys()] |
| main(input_directory=args.input_directory, |
| output_directory=output_directory, |
| caravel_root=args.caravel_root, |
| pdk_root=args.pdk_root, |
| private=args.private, |
| sequence=sequence, |
| log_path=log_path, |
| default_content='_default_content') |