mirror of
https://github.com/The-OpenROAD-Project/OpenLane.git
synced 2026-06-03 00:08:08 +08:00
+ Added linting rules for flake8, formatting with black + Added a GitHub Action to enforce lint/format rules
422 lines
12 KiB
Python
Executable File
422 lines
12 KiB
Python
Executable File
#!/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.
|
|
|
|
"""
|
|
See <openlane_root>/docs/source/using_or_issue.md.
|
|
|
|
This script is intended for use by bug reporters and maintainers and is not part of the flow.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import glob
|
|
import shutil
|
|
import pathlib
|
|
import argparse
|
|
from typing import List
|
|
from collections import deque
|
|
from os.path import join, abspath, dirname, basename, isdir, relpath
|
|
|
|
openlane_path = abspath(dirname(dirname(__file__)))
|
|
|
|
parser = argparse.ArgumentParser(description="OpenROAD Issue Packager")
|
|
parser.add_argument(
|
|
"--or-script",
|
|
"-s",
|
|
required=True,
|
|
help="Path to the OpenROAD script causing the failure: i.e. ./scripts/openroad/antenna_check.tcl, ./scripts/openroad/pdn.tcl, etc. [required]",
|
|
)
|
|
parser.add_argument(
|
|
"--pdk-root",
|
|
required=(os.getenv("PDK_ROOT") is None),
|
|
default=os.getenv("PDK_ROOT"),
|
|
help="Path to the PDK root [required if environment variable PDK_ROOT is not set]",
|
|
)
|
|
parser.add_argument(
|
|
"--run-path",
|
|
"-r",
|
|
default=None,
|
|
help="The run path. If not specified, the script will attempt to discern it from the input_def path.",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
"-o",
|
|
default="./out.def",
|
|
help="Name of def file to be generated [default: ./out.def]",
|
|
)
|
|
parser.add_argument(
|
|
"--verbose",
|
|
action="store_true",
|
|
default=False,
|
|
help="Verbose output of all found environment variables.",
|
|
)
|
|
parser.add_argument(
|
|
"--netlist",
|
|
"-n",
|
|
action="store_true",
|
|
default=False,
|
|
help="Use the netlist as an input instead of a def file. Useful for evaluating some scripts such as floorplan.tcl.",
|
|
)
|
|
parser.add_argument("--output-dir", default=None, help="Output to this directory.")
|
|
parser.add_argument(
|
|
"input",
|
|
help="Name of input into the OR script (usually denoted by environment variable CURRENT_NETLIST or CURRENT_DEF: get it from the logs) [required]",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
OPEN_SOURCE_PDKS = ["sky130A"]
|
|
print(
|
|
"""
|
|
or_issue.py OpenROAD Issue Packager
|
|
|
|
EFABLESS CORPORATION AND ALL AUTHORS OF THE OPENLANE PROJECT SHALL NOT BE HELD
|
|
LIABLE FOR ANY LEAKS THAT MAY OCCUR TO ANY PROPRIETARY DATA AS A RESULT OF USING
|
|
THIS SCRIPT. THIS SCRIPT IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
CONDITIONS OF ANY KIND.
|
|
|
|
BY USING THIS SCRIPT, YOU ACKNOWLEDGE THAT YOU FULLY UNDERSTAND THIS DISCLAIMER
|
|
AND ALL IT ENTAILS.
|
|
""",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
script_path = abspath(args.or_script)
|
|
pdk_root = abspath(args.pdk_root)
|
|
or_scripts_path = join(openlane_path, "scripts", "openroad")
|
|
use_netlist = args.netlist
|
|
input_file = abspath(args.input)
|
|
run_path = None
|
|
if args.run_path is not None:
|
|
run_path = abspath(args.run_path)
|
|
else:
|
|
current_dir = dirname(input_file)
|
|
while current_dir != "/":
|
|
if "config.tcl" in os.listdir(current_dir):
|
|
run_path = current_dir
|
|
break
|
|
current_dir = dirname(current_dir)
|
|
|
|
if run_path is None:
|
|
print(
|
|
f"[ERR] No run path provided and {input_file} is not in the run path.",
|
|
file=sys.stderr,
|
|
)
|
|
exit(os.EX_USAGE)
|
|
else:
|
|
print(f"[INF] Resolved run path to {run_path}.")
|
|
|
|
save_def = args.output
|
|
verbose = args.verbose
|
|
|
|
if not os.path.exists(input_file):
|
|
print(f"[ERR] {input_file} not found.", file=sys.stderr)
|
|
exit(os.EX_NOINPUT)
|
|
|
|
if not script_path.startswith(or_scripts_path):
|
|
print(
|
|
f"[ERR] The OpenROAD script {script_path} does not appear to be in {or_scripts_path}.",
|
|
file=sys.stderr,
|
|
)
|
|
exit(os.EX_CONFIG)
|
|
|
|
if not os.path.exists(run_path) and os.path.isdir(run_path):
|
|
print(f"[ERR] The run path {run_path} is not a valid folder.", file=sys.stderr)
|
|
exit(os.EX_CONFIG)
|
|
|
|
run_name = basename(run_path)
|
|
|
|
# Phase 1: Read All Environment Variables
|
|
# pdk_config = join(args.pdk_root, "sky130A", "libs.tech", "openlane", "config.tcl")
|
|
print("Parsing config file(s)…", file=sys.stderr)
|
|
run_config = join(run_path, "config.tcl")
|
|
|
|
env = {}
|
|
|
|
|
|
def read_env(config_path: str, from_path: str, input_env={}) -> dict:
|
|
rx = r"\s*set\s*::env\((.+?)\)\s*(.+)"
|
|
env = input_env.copy()
|
|
string_data = ""
|
|
try:
|
|
string_data = open(config_path).read()
|
|
except FileNotFoundError:
|
|
print(
|
|
f"❌ File {config_path} not found. The path {from_path} may have been specified incorrectly.",
|
|
file=sys.stderr,
|
|
)
|
|
exit(os.EX_NOINPUT)
|
|
|
|
# Process \ at ends of lines, remove semicolons
|
|
entries = string_data.split("\n")
|
|
i = 0
|
|
while i < len(entries):
|
|
if not entries[i].endswith("\\"):
|
|
if entries[i].endswith(";"):
|
|
entries[i] = entries[i][:-1]
|
|
i += 1
|
|
continue
|
|
entries[i] = entries[i][:-1] + entries[i + 1]
|
|
del entries[i + 1]
|
|
|
|
for entry in entries:
|
|
match = re.match(rx, entry)
|
|
if match is None:
|
|
continue
|
|
name = match[1]
|
|
value = match[2]
|
|
# remove double quotes/{}
|
|
value = value.strip('"')
|
|
value = value.strip("{}")
|
|
env[name] = value
|
|
|
|
return env
|
|
|
|
|
|
env = read_env(run_config, "Run Path") # , read_env(pdk_config, "PDK Root"))
|
|
|
|
# Cannot be reliably read from config.tcl
|
|
input_key = "CURRENT_DEF"
|
|
if use_netlist:
|
|
input_key = "CURRENT_NETLIST"
|
|
env[input_key] = input_file
|
|
env["SAVE_DEF"] = save_def
|
|
|
|
|
|
# Phase 2: Set up destination folder
|
|
script_basename = basename(args.or_script)[:-4]
|
|
destination_folder = args.output_dir or abspath(
|
|
join(".", "_build", f"{run_name}_{script_basename}_packaged")
|
|
)
|
|
print(f"Setting up {destination_folder}…", file=sys.stderr)
|
|
|
|
|
|
def mkdirp(path):
|
|
return pathlib.Path(path).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
try:
|
|
shutil.rmtree(destination_folder)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
mkdirp(destination_folder)
|
|
|
|
# Phase 3: Process TCL Scripts To Find Full List Of Files
|
|
tcls_to_process = deque([script_path])
|
|
|
|
|
|
def shift(deque):
|
|
try:
|
|
return deque.popleft()
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
or_script_counter = 0
|
|
|
|
|
|
def get_or_script():
|
|
global or_script_counter
|
|
value = f"OR_SCRIPT_{or_script_counter}"
|
|
or_script_counter += 1
|
|
return value
|
|
|
|
|
|
env_keys_used = set()
|
|
tcls = set()
|
|
current = shift(tcls_to_process)
|
|
while current is not None:
|
|
env_key = get_or_script()
|
|
env_keys_used.add(env_key)
|
|
env[env_key] = current
|
|
|
|
try:
|
|
script = open(current).read()
|
|
|
|
for key, value in env.items():
|
|
key_accessor = re.compile(rf"((\$::env\({re.escape(key)}\))([/\-\w\.]*))")
|
|
for use in key_accessor.findall(script):
|
|
use: List[str]
|
|
full, accessor, extra = use
|
|
env_keys_used.add(key)
|
|
|
|
value_substituted = full.replace(accessor, value)
|
|
|
|
if value_substituted.endswith(".tcl") or value_substituted.endswith(
|
|
".sdc"
|
|
):
|
|
if value_substituted not in tcls:
|
|
tcls.add(value_substituted)
|
|
tcls_to_process.append(value_substituted)
|
|
except Exception:
|
|
print(
|
|
f"[WRN] {current} was not found, might be a product. Skipping",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
current = shift(tcls_to_process)
|
|
|
|
# Phase 4: Copy The Files
|
|
final_env = {}
|
|
|
|
pdk_path = join(destination_folder, "pdk")
|
|
openlane_misc_path = join(destination_folder, "openlane")
|
|
|
|
warnings = []
|
|
|
|
|
|
def copy(frm, to):
|
|
parents = dirname(to)
|
|
mkdirp(parents)
|
|
|
|
def do_copy():
|
|
if isdir(frm):
|
|
shutil.copytree(frm, to)
|
|
else:
|
|
shutil.copyfile(frm, to)
|
|
|
|
try:
|
|
incomplete_matches = glob.glob(frm + "*")
|
|
|
|
if len(incomplete_matches) == 0:
|
|
raise Exception()
|
|
elif len(incomplete_matches) != 1 or incomplete_matches[0] != frm:
|
|
# Prefix For Other Files
|
|
for match in incomplete_matches:
|
|
if match == frm:
|
|
# If it's both a file AND a prefix for other files
|
|
do_copy()
|
|
else:
|
|
new_frm = match
|
|
new_to = to + new_frm[len(frm) :]
|
|
copy(new_frm, new_to)
|
|
else:
|
|
do_copy()
|
|
except Exception as e:
|
|
warnings.append(f"[WRN] Couldn't copy {frm}: {e}. Skipped.")
|
|
|
|
|
|
if verbose:
|
|
print("\nProcessing environment variables…\n---", file=sys.stderr)
|
|
for key in env_keys_used:
|
|
value = env[key]
|
|
if verbose:
|
|
print(f"{key}: {value}")
|
|
if value == input_file:
|
|
final_path = join(destination_folder, "in.def")
|
|
from_path = value
|
|
copy(from_path, final_path)
|
|
|
|
final_env[input_key] = "./in.def"
|
|
elif value.startswith(run_path):
|
|
relative = relpath(value, run_path)
|
|
final_value = join(".", relative)
|
|
final_path = join(destination_folder, final_value)
|
|
from_path = value
|
|
copy(from_path, final_path)
|
|
|
|
final_env[key] = final_value
|
|
elif value.startswith(pdk_root):
|
|
nonfree_warning = True
|
|
value_components = value.split(os.path.sep)
|
|
for pdk in OPEN_SOURCE_PDKS:
|
|
if pdk in value_components:
|
|
nonfree_warning = False
|
|
if nonfree_warning:
|
|
warnings.append(
|
|
f"[WRN] {value} appears to be a confidential PDK file. ENSURE THAT YOU INSPECT THE RESULTS."
|
|
)
|
|
relative = relpath(value, pdk_root)
|
|
final_value = join("pdk", relative)
|
|
final_path = join(destination_folder, final_value)
|
|
copy(value, final_path)
|
|
final_env[key] = final_value
|
|
elif value.startswith("/openlane"):
|
|
relative = relpath(value, "/openlane")
|
|
final_value = join("openlane", relative)
|
|
final_path = join(destination_folder, final_value)
|
|
from_path = value.replace("/openlane", openlane_path)
|
|
if value != "/openlane/scripts": # Too many files to copy otherwise
|
|
copy(from_path, final_path)
|
|
final_env[key] = final_value
|
|
elif value.startswith("/"):
|
|
final_value = value[1:]
|
|
final_path = join(destination_folder, final_value)
|
|
copy(value, final_path)
|
|
final_env[key] = final_value
|
|
else:
|
|
final_env[key] = value
|
|
if verbose:
|
|
print("---\n", file=sys.stderr)
|
|
|
|
for warning in warnings:
|
|
print(warning)
|
|
print("\n")
|
|
|
|
# Phase 5: Create Environment Set/Run Files
|
|
run_shell = join(destination_folder, "run.sh")
|
|
with open(run_shell, "w") as f:
|
|
env_list = "\n".join(
|
|
[f"export {key}='{value}';" for key, value in final_env.items()]
|
|
)
|
|
f.write(
|
|
f"""\
|
|
#!/bin/sh
|
|
dir=$(cd -P -- "$(dirname -- "$0")" && pwd -P)
|
|
cd $dir;
|
|
{env_list}
|
|
OPENROAD_BIN=${{OPENROAD_BIN:-openroad}}
|
|
$OPENROAD_BIN -exit $OR_SCRIPT_0
|
|
"""
|
|
)
|
|
os.chmod(run_shell, 0o755)
|
|
|
|
run_tcl = join(destination_folder, "run.tcl")
|
|
with open(run_tcl, "w") as f:
|
|
env_list = "\n".join(
|
|
[f"set ::env({key}) {{{value}}};" for key, value in final_env.items()]
|
|
)
|
|
f.write(
|
|
f"""\
|
|
#!/usr/bin/env openroad
|
|
{env_list}
|
|
source $::env(OR_SCRIPT_0)
|
|
"""
|
|
)
|
|
os.chmod(run_tcl, 0o755)
|
|
|
|
gdb_env = join(destination_folder, "env.gdb")
|
|
with open(gdb_env, "w") as f:
|
|
env_list = "\n".join([f"set env {key} {value}" for key, value in final_env.items()])
|
|
f.write(
|
|
f"""\
|
|
{env_list}
|
|
"""
|
|
)
|
|
|
|
lldb_env = join(destination_folder, "env.lldb")
|
|
with open(lldb_env, "w") as f:
|
|
env_list = "\n".join([f"env {key}={value}" for key, value in final_env.items()])
|
|
f.write(
|
|
f"""\
|
|
{env_list}
|
|
"""
|
|
)
|
|
|
|
print("[FIN] Done.", file=sys.stderr)
|
|
print(destination_folder)
|