blob: cdef7c0b2012320e0465458e9a1e6132341b7748 [file] [log] [blame]
# 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 logging
import os
import sys
from collections import OrderedDict
from pathlib import Path
from checks import defaults_check
from checks import documentation_check
from checks import makefile_check
from checks import manifest_check
from checks.consistency_check import consistency_check
from checks.drc_checks.klayout import klayout_gds_drc_check
from checks.drc_checks.magic import magic_gds_drc_check
from checks.license_check import license_check
from checks.utils import utils
from checks.xor_check import xor_check
class CheckManagerNotFound(Exception):
pass
class CheckManager:
def __init__(self, precheck_config, project_config):
self.precheck_config = precheck_config
self.project_config = project_config
self.result = True
def run(self):
"""
Define the check running steps. This version does nothing and is intended to be implemented by subclasses.
"""
class Consistency(CheckManager):
__ref__ = 'consistency'
__surname__ = 'Consistency'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.output_directory = self.precheck_config['output_directory']
self.input_directory = self.precheck_config['input_directory']
def run(self):
empty_wrapper_url = f"{self.project_config['link_prefix']}/verilog/rtl/__{self.project_config['user_module']}.v"
golden_wrapper_file_path = self.output_directory / 'outputs' / f"__{self.project_config['user_module']}.v"
utils.download_gzip_file_from_url(empty_wrapper_url, golden_wrapper_file_path)
defines_url = f"{self.project_config['link_prefix']}/verilog/rtl/defines.v"
defines_file_path = self.output_directory / 'outputs/defines.v'
utils.download_gzip_file_from_url(defines_url, defines_file_path)
self.result = consistency_check.main(input_directory=self.input_directory,
output_directory=self.output_directory,
project_config=self.project_config,
golden_wrapper_netlist=golden_wrapper_file_path,
defines_file_path=defines_file_path)
if self.result:
logging.info("{{CONSISTENCY CHECK PASSED}} The user netlist and the top netlist are valid.")
else:
logging.warning("{{CONSISTENCY CHECK FAILED}} The user netlist and the top netlist are not valid.")
return self.result
class Defaults(CheckManager):
__ref__ = 'default'
__surname__ = 'Default'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
def run(self):
default_readme_result = defaults_check.has_default_readme(self.precheck_config['input_directory'], self.precheck_config['default_content'])
if default_readme_result:
logging.info("{{README DEFAULT CHECK PASSED}} Project 'README.md' was modified and is not identical to the default 'README.md'")
else:
self.result = False
logging.warning("{{README DEFAULT CHECK FAILED}} Project 'README.md' was not modified and is identical to the default 'README.md'")
default_content_result = defaults_check.has_default_content(self.precheck_config['input_directory'], self.precheck_config['default_content'])
if default_content_result:
logging.info("{{CONTENT DEFAULT CHECK PASSED}} Project 'gds' was modified and is not identical to the default 'gds'")
else:
self.result = False
logging.warning("{{CONTENT DEFAULT CHECK FAILED}} Project 'gds' was not modified and is identical to the default 'gds'")
return self.result
class Documentation(CheckManager):
__ref__ = 'documentation'
__surname__ = 'Documentation'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
def run(self):
self.result = documentation_check.main(input_directory=self.precheck_config['input_directory'])
if self.result:
logging.info("{{DOCUMENTATION CHECK PASSED}} Project documentation is appropriate.")
else:
logging.warning("{{DOCUMENTATION CHECK FAILED}} Project documentation is not appropriate.")
return self.result
class KlayoutDRC(CheckManager):
__ref__ = None
__surname__ = None
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.gds_input_file_path = self.precheck_config['input_directory'] / f"gds/{project_config['user_module']}.gds"
self.drc_script_path = ""
self.klayout_cmd_extra_args = []
def run(self):
if not self.gds_input_file_path.exists():
self.result = False
logging.warning(f"{{{{{self.__surname__} CHECK FAILED}}}} {self.gds_input_file_path.name}, GDS file was not found.")
return self.result
self.result = klayout_gds_drc_check.klayout_gds_drc_check(self.__ref__,
self.drc_script_path,
self.gds_input_file_path,
self.precheck_config['output_directory'],
self.klayout_cmd_extra_args)
if self.result:
logging.info(f"{{{{{self.__surname__} CHECK PASSED}}}} The GDS file, {self.gds_input_file_path.name}, has no DRC violations.")
else:
logging.warning(f"{{{{{self.__surname__} CHECK FAILED}}}} The GDS file, {self.gds_input_file_path.name}, has DRC violations.")
return self.result
class KlayoutBEOL(KlayoutDRC):
__ref__ = 'klayout_beol'
__surname__ = 'Klayout BEOL'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/tech-files/sky130A_mr.drc"
self.klayout_cmd_extra_args = ['-rd', 'beol=true']
class KlayoutFEOL(KlayoutDRC):
__ref__ = 'klayout_feol'
__surname__ = 'Klayout FEOL'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/tech-files/sky130A_mr.drc"
self.klayout_cmd_extra_args = ['-rd', 'feol=true']
# TODO: discuss migration to tapeout and removal from precheck
class KlayoutFOMDensity(KlayoutDRC):
__ref__ = 'klayout_fom_density'
__surname__ = 'Klayout Field Oxide Mask Density'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/drc_checks/klayout/fom_density.lydrc"
class KlayoutMetalMinimumClearAreaDensity(KlayoutDRC):
__ref__ = 'klayout_met_min_ca_density'
__surname__ = 'Klayout Metal Minimum Clear Area Density'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/drc_checks/klayout/met_min_ca_density.lydrc"
class KlayoutOffgrid(KlayoutDRC):
__ref__ = 'klayout_offgrid'
__surname__ = 'Klayout Offgrid'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/drc_checks/klayout/offgrid.lydrc"
class KlayoutPinLabelPurposesOverlappingDrawing(KlayoutDRC):
__ref__ = 'klayout_pin_label_purposes_overlapping_drawing'
__surname__ = 'Klayout Pin Label Purposes Overlapping Drawing'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/drc_checks/klayout/pin_label_purposes_overlapping_drawing.rb.drc"
self.klayout_cmd_extra_args = ['-rd', f'top_cell_name={self.project_config["user_module"]}']
class KlayoutZeroArea(KlayoutDRC):
__ref__ = 'klayout_zeroarea'
__surname__ = 'Klayout ZeroArea'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.drc_script_path = Path(__file__).parent.parent / "checks/drc_checks/klayout/zeroarea.rb.drc"
self.klayout_cmd_extra_args = ["-rd", f"""cleaned_output={self.precheck_config['output_directory'] / 'outputs' / f"{self.gds_input_file_path.stem}_no_zero_areas.gds"}"""]
class License(CheckManager):
__ref__ = 'license'
__surname__ = 'License'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
def run(self):
license_check_result = license_check.verify_license_compliance(self.precheck_config['input_directory'])
if license_check_result:
logging.info("{{MAIN LICENSE CHECK PASSED}} An approved LICENSE was found in project root.")
else:
self.result = False
logging.warning("{{MAIN LICENSE CHECK FAILED}} A prohibited LICENSE was found in project root.")
submodules_license_check_result = license_check.check_submodules_licenses(self.precheck_config['input_directory'])
if submodules_license_check_result:
logging.info("{{SUBMODULES LICENSE CHECK PASSED}} No prohibited LICENSE file(s) was found in project submodules")
else:
self.result = False
logging.warning("{{SUBMODULES LICENSE CHECK FAILED}} A prohibited LICENSE file(s) was found in project submodules")
third_party_libraries_path = self.precheck_config['input_directory'] / 'third_party'
if third_party_libraries_path.exists():
third_party_libs_license_check_result = license_check.check_third_party_libs_licenses(third_party_libraries_path)
if third_party_libs_license_check_result:
logging.info("{{THIRD PARTY LIBRARIES LICENSE CHECK PASSED}} No prohibited LICENSE file(s) was found in project 'third_party' directory")
else:
self.result = False
logging.warning("{{THIRD PARTY LIBRARIES LICENSE CHECK FAILED}} A prohibited LICENSE file(s) was found in project 'third_party' directory")
spdx_non_compliant_list = license_check.check_dir_spdx_compliance([], self.precheck_config['input_directory'], license_check_result)
if not spdx_non_compliant_list:
logging.info("{{SPDX COMPLIANCE CHECK PASSED}} Project is compliant with the SPDX Standard")
else:
paths = [str(x) for x in spdx_non_compliant_list[:15]] if spdx_non_compliant_list.__len__() >= 15 else [str(x) for x in spdx_non_compliant_list]
logging.warning(f"{{{{SPDX COMPLIANCE CHECK FAILED}}}} Found {spdx_non_compliant_list.__len__()} non-compliant file(s) with the SPDX Standard.")
logging.info(f"SPDX COMPLIANCE: NON-COMPLIANT FILE(S) PREVIEW: {paths}")
try:
if not self.precheck_config['output_directory'].exists(): # note: needed if check is used independently to create output dirs
os.makedirs(self.precheck_config['output_directory'])
spdx_compliance_report_path = self.precheck_config['output_directory'] / "logs/spdx_compliance_report.log"
with open(spdx_compliance_report_path, mode='w+') as f:
logging.info(f"For the full SPDX compliance report check: {spdx_compliance_report_path}")
[f.write(f"{str(x)}\n") for x in spdx_non_compliant_list]
except OSError as os_error:
logging.fatal(f"{{{{SPDX COMPLIANCE EXCEPTION}}}} Failed to create SPDX compliance report: {os_error}")
sys.exit(253)
return self.result
class MagicDRC(CheckManager):
__ref__ = 'magic_drc'
__surname__ = 'Magic DRC'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
self.gds_input_file_path = self.precheck_config['input_directory'] / f"gds/{self.project_config['user_module']}.gds"
def run(self):
if not self.gds_input_file_path.exists():
self.result = False
logging.warning(f"{{{{MAGIC DRC CHECK FAILED}}}} The GDS file, {self.gds_input_file_path.name}, was not found.")
return self.result
self.result = magic_gds_drc_check.magic_gds_drc_check(self.gds_input_file_path,
self.project_config['user_module'],
self.precheck_config['pdk_root'],
self.precheck_config['output_directory'])
if self.result:
logging.info(f"{{{{MAGIC DRC CHECK PASSED}}}} The GDS file, {self.gds_input_file_path.name}, has no DRC violations.")
else:
logging.warning(f"{{{{MAGIC DRC CHECK FAILED}}}} The GDS file, {self.gds_input_file_path.name}, has DRC violations.")
return self.result
class Makefile(CheckManager):
__ref__ = 'makefile'
__surname__ = 'Makefile'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
def run(self):
self.result = makefile_check.main(input_directory=self.precheck_config['input_directory'])
if self.result:
logging.info("{{MAKEFILE CHECK PASSED}} Makefile valid.")
else:
logging.warning("{{MAKEFILE CHECK FAILED}} Makefile file is not valid.")
return self.result
class Manifest(CheckManager):
__ref__ = 'manifest'
__surname__ = 'Manifest'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
def run(self):
caravel_root = self.precheck_config['caravel_root']
self.result = manifest_check.main(input_directory=caravel_root, output_directory=self.precheck_config['output_directory'], manifest_source='master')
if self.result:
logging.info("{{MANIFEST CHECKS PASSED}} Manifest Checks Passed. Caravel version matches.")
else:
logging.warning("{{MANIFEST CHECKS FAILED}} Manifest checks failed. Caravel version does not match. Please rebase your Repository to the latest Caravel master.")
return self.result
class XOR(CheckManager):
__ref__ = 'xor'
__surname__ = 'XOR'
def __init__(self, precheck_config, project_config):
super().__init__(precheck_config, project_config)
def run(self):
# TODO(nofal): This should be a single file across the entire precheck
# We should have a good reason to have it in the XORCheck, it should be
# part of a constant variables file that defines project-wide variables
magicrc_file_path = self.precheck_config['pdk_root'] / 'sky130A' / 'libs.tech' / 'magic' / 'sky130A.magicrc'
empty_wrapper_url = f"{self.project_config['link_prefix']}/gds/{self.project_config['golden_wrapper']}.gds.gz"
gds_golden_wrapper_file_path = self.precheck_config['output_directory'] / 'outputs' / f"{self.project_config['golden_wrapper']}.gds"
utils.download_gzip_file_from_url(empty_wrapper_url, gds_golden_wrapper_file_path)
self.result = xor_check.gds_xor_check(self.precheck_config['input_directory'], self.precheck_config['output_directory'],
magicrc_file_path, gds_golden_wrapper_file_path, self.project_config)
if self.result:
logging.info("{{XOR CHECK PASSED}} The GDS file has no XOR violations.")
else:
logging.warning("{{XOR CHECK FAILED}} The GDS file has non-conforming geometries.")
return self.result
# Note: list of checks for an public (open source) project
open_source_checks = OrderedDict([
(License.__ref__, License),
(Manifest.__ref__, Manifest),
(Makefile.__ref__, Makefile),
(Defaults.__ref__, Defaults),
(Documentation.__ref__, Documentation),
(Consistency.__ref__, Consistency),
(XOR.__ref__, XOR),
(MagicDRC.__ref__, MagicDRC),
(KlayoutFEOL.__ref__, KlayoutFEOL),
(KlayoutBEOL.__ref__, KlayoutBEOL),
(KlayoutOffgrid.__ref__, KlayoutOffgrid),
(KlayoutMetalMinimumClearAreaDensity.__ref__, KlayoutMetalMinimumClearAreaDensity),
(KlayoutPinLabelPurposesOverlappingDrawing.__ref__, KlayoutPinLabelPurposesOverlappingDrawing),
(KlayoutZeroArea.__ref__, KlayoutZeroArea)
])
# Note: list of checks for a private project
private_checks = OrderedDict([
(Manifest.__ref__, Manifest),
(Makefile.__ref__, Makefile),
(Consistency.__ref__, Consistency),
(XOR.__ref__, XOR),
(MagicDRC.__ref__, MagicDRC),
(KlayoutFEOL.__ref__, KlayoutFEOL),
(KlayoutBEOL.__ref__, KlayoutBEOL),
(KlayoutOffgrid.__ref__, KlayoutOffgrid),
(KlayoutMetalMinimumClearAreaDensity.__ref__, KlayoutMetalMinimumClearAreaDensity),
(KlayoutPinLabelPurposesOverlappingDrawing.__ref__, KlayoutPinLabelPurposesOverlappingDrawing),
(KlayoutZeroArea.__ref__, KlayoutZeroArea)
])
def get_check_manager(name, *args, **kwargs):
check_managers = args[0]['check_managers']
if name.lower() in check_managers.keys():
return check_managers[name.lower()](*args, **kwargs)
else:
raise CheckManagerNotFound(f"The check '{name.lower()}' does not exist")