mirror of
https://github.com/The-OpenROAD-Project/OpenLane.git
synced 2026-05-29 00:23:55 +08:00
Please excuse the last commit lacking a changelog. + PDK installation uses Volare, transparently to the user: all they have to do is type "make", where it will get OpenLane and the PDK + CI now uses Volare to either **get** or **build** the PDK (if not found), which speeds up the fastest test set by around 60%. + Added rudimentary dependency installation instructions. + pyyaml folded into the repo, so users without pip can still run issue surveys ~ Open PDKs updated: Parasitics are now extracted using a rules file based on [spef-extractor](https://github.com/Cloud-V/spef-extractor) as a ***temporary*** measure ~ Issue survey no longer checks for click and pyyaml: a venv is used in those scenarios. ~ OpenLane build no longer uses the host filesystem as an intermediary, instead using a templated dockerfile with an N-stage build for N tools, saving IO operations (40% improvement measured) ~ Old PDK targets renamed to build-pdk-conda, includes SRAM by default ~ Replaced python3 ./env.py issue-survey with `make survey` - Removed Fault from documentation (until I get the chance to work on it)
250 lines
9.5 KiB
Python
250 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2021 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.
|
|
|
|
import os
|
|
import re
|
|
import io
|
|
import sys
|
|
import json
|
|
import pathlib
|
|
import traceback
|
|
from os.path import dirname, abspath, join
|
|
from typing import Optional
|
|
|
|
sys.path.append(os.path.dirname(__file__))
|
|
import includedyaml as yaml # noqa: E402
|
|
|
|
openlane_dir = abspath(dirname(dirname(__file__)))
|
|
|
|
|
|
def verify_versions(
|
|
no_pdks: bool = False,
|
|
no_tools: bool = False,
|
|
report_file: io.TextIOBase = sys.stderr,
|
|
pdk: Optional[str] = os.getenv("PDK"),
|
|
):
|
|
# 1. Load Current Flow Script Manifest
|
|
manifest = None
|
|
try:
|
|
flow_script_manifest_path = join(
|
|
openlane_dir, "dependencies", "tool_metadata.yml"
|
|
)
|
|
manifest = yaml.safe_load(open(flow_script_manifest_path))
|
|
except FileNotFoundError:
|
|
raise Exception(
|
|
"Flow script tool manifest not found. This is a fatal error."
|
|
) # Is this even possible?
|
|
|
|
manifest_dict = {element["name"]: element for element in manifest}
|
|
mismatches = False
|
|
|
|
manifest_names_by_SOURCES_name = {
|
|
"open_pdks": "open_pdks",
|
|
"skywater": "sky130",
|
|
"magic": "magic",
|
|
}
|
|
pdk_manifest_names = set(manifest_names_by_SOURCES_name.values())
|
|
|
|
if not no_pdks:
|
|
try:
|
|
# 2. Check if the PDK is compatible with Flow Scripts
|
|
pdk_root = os.getenv("PDK_ROOT")
|
|
if not os.getenv("PDK_ROOT"):
|
|
pdk_root = join(openlane_dir, "pdks")
|
|
|
|
if pdk is not None:
|
|
pdk_dir = join(pdk_root, pdk)
|
|
|
|
if not pathlib.Path(pdk_dir).is_dir():
|
|
raise Exception(f"{pdk_dir} not found.")
|
|
|
|
tool_versions = []
|
|
|
|
sources_file = join(pdk_dir, "SOURCES")
|
|
config_file = join(pdk_dir, ".config", "nodeinfo.json")
|
|
|
|
if os.path.isfile(sources_file):
|
|
sources_str = None
|
|
try:
|
|
sources_str = open(sources_file).read()
|
|
except FileNotFoundError:
|
|
raise Exception(
|
|
f"Could not find SOURCES file for the installed {pdk} PDK."
|
|
)
|
|
|
|
sources_str = sources_str.strip()
|
|
|
|
# Format: {tool} {commit}
|
|
#
|
|
# This regex also handles an issue where older versions used the
|
|
# non-standard echo -ne command, where the format is -ne {tool}\n{commit}\n.
|
|
#
|
|
name_rx = re.compile(
|
|
r"(?:\-ne\s+)?([\w\-]+)\s+([A-Fa-f0-9]+)", re.MULTILINE
|
|
)
|
|
|
|
for tool_match in name_rx.finditer(sources_str):
|
|
name = tool_match[1]
|
|
commit = tool_match[2]
|
|
|
|
manifest_name = manifest_names_by_SOURCES_name.get(name)
|
|
if manifest_name is None:
|
|
continue
|
|
|
|
tool_versions.append((manifest_name, commit))
|
|
elif os.path.isfile(config_file):
|
|
config_str = open(config_file).read()
|
|
try:
|
|
config = json.loads(config_str)
|
|
commit_set = config["commit"]
|
|
if type(commit_set) == str:
|
|
tool_versions.append(("open_pdks", commit_set))
|
|
else:
|
|
for key, value in commit_set.items():
|
|
# Handle bug in some older versions of opdks where the magic commit field is empty.
|
|
if value.strip() == "":
|
|
continue
|
|
tool_versions.append((key, value))
|
|
except json.decoder.JSONDecodeError:
|
|
raise Exception("Malformed .config/nodeinfo.json.")
|
|
|
|
else:
|
|
raise Exception(
|
|
"Neither SOURCES nor .config/nodeinfo.json exist in the PDK."
|
|
)
|
|
|
|
for name, commit in tool_versions:
|
|
manifest_commit = manifest_dict[name]["commit"]
|
|
|
|
if commit != manifest_commit:
|
|
mismatches = True
|
|
print(
|
|
f"The version of {name} used in building the PDK does not match the version OpenLane was tested on (installed: {commit}, tested: {manifest_commit})",
|
|
file=report_file,
|
|
)
|
|
print(
|
|
"This may introduce some issues. You may want to re-install the PDK by invoking `make pdk`.",
|
|
file=report_file,
|
|
)
|
|
|
|
pdk_manifest_names.add(name)
|
|
except Exception as e:
|
|
print(e, file=report_file)
|
|
print(traceback.format_exc(), file=report_file)
|
|
raise Exception("Failed to compare PDKs.")
|
|
|
|
if not no_tools:
|
|
installed = os.getenv("OPENLANE_LOCAL_INSTALL") == "1"
|
|
environment_manifest = None
|
|
if installed:
|
|
# 3a. Compare with installed versions
|
|
installed_versions_path = join(
|
|
os.environ["OL_INSTALL_DIR"], "build", "versions"
|
|
)
|
|
environment_manifest = []
|
|
for tool in os.listdir(installed_versions_path):
|
|
protocol, url, commit = (
|
|
open(join(installed_versions_path, tool)).read().split(":")
|
|
)
|
|
repo = f"{protocol}:{url}"
|
|
environment_manifest.append(
|
|
{"name": tool, "repo": repo, "commit": commit}
|
|
)
|
|
else:
|
|
# 3b. Compare Container And Installation Manifests
|
|
try:
|
|
container_manifest_path = join("/", "tool_metadata.yml")
|
|
environment_manifest = yaml.safe_load(open(container_manifest_path))
|
|
except FileNotFoundError:
|
|
raise Exception(
|
|
"Container manifest not found. What this likely means is that the container is severely out of date."
|
|
)
|
|
|
|
tool_set_flow = (
|
|
set([element["name"] for element in manifest]) - pdk_manifest_names
|
|
)
|
|
tool_set_container = (
|
|
set([element["name"] for element in environment_manifest])
|
|
- pdk_manifest_names
|
|
)
|
|
|
|
unmatched_tools_flow = tool_set_flow - tool_set_container
|
|
for tool in unmatched_tools_flow:
|
|
tool_object = manifest_dict[tool]
|
|
if (
|
|
tool_object.get("in_container") is not None
|
|
and not tool_object["in_container"]
|
|
):
|
|
continue
|
|
if (
|
|
installed
|
|
and tool_object.get("in_install") is not None
|
|
and not tool_object["in_install"]
|
|
):
|
|
continue
|
|
print(
|
|
f"Tool {tool} is required by the flow scripts being used, but appears to not be installed in the environment.",
|
|
file=report_file,
|
|
)
|
|
mismatches = True
|
|
|
|
unmatched_tools_container = tool_set_container - tool_set_flow
|
|
for tool in unmatched_tools_container:
|
|
print(
|
|
f"Tool {tool} is installed in the environment, but has no corresponding entry in the flow scripts.",
|
|
file=report_file,
|
|
)
|
|
mismatches = True
|
|
|
|
for tool in environment_manifest:
|
|
if tool["name"] in pdk_manifest_names:
|
|
continue # PDK Stuff Already Checked
|
|
flow_script_counterpart = manifest_dict.get(tool["name"])
|
|
if flow_script_counterpart is None:
|
|
continue
|
|
container_commit = tool["commit"]
|
|
flow_script_commit = flow_script_counterpart["commit"]
|
|
if container_commit != flow_script_commit:
|
|
print(
|
|
f"The version of {tool['name']} installed in the environment does not match the one required by the OpenLane flow scripts (installed: {container_commit}, expected: {flow_script_commit})",
|
|
file=report_file,
|
|
)
|
|
mismatches = True
|
|
|
|
return mismatches
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
no_tools = False
|
|
no_pdks = False
|
|
mismatches = os.getenv("TEST_MISMATCHES") or "all"
|
|
if mismatches == "none":
|
|
no_tools = True
|
|
no_pdks = True
|
|
elif mismatches == "pdk":
|
|
no_tools = True
|
|
elif mismatches == "tools":
|
|
no_pdks = True
|
|
elif mismatches != "all":
|
|
print(f"Unknown mismatch test '{mismatches}'.", file=sys.stderr)
|
|
sys.exit(-1)
|
|
mismatches = verify_versions(no_tools=no_tools, no_pdks=no_pdks)
|
|
sys.exit(os.EX_CONFIG if mismatches else os.EX_OK)
|
|
except Exception as e:
|
|
print(f"{e}")
|
|
sys.exit(os.EX_UNAVAILABLE)
|