Files
tt-support-tools/configure.py
T
Kristaps Jurkans 5779f397bc feat: add interactive document checker (#164)
* feat: add interactive document checker

This utility can be used to audit the documentation of the projects on the shuttle, marking them to be removed from the datasheet.

Usage:
`python3 tt/configure.py --doc-check`, inspect using `nano` controls, then answer prompt upon exiting `nano`.
2026-03-30 15:15:24 +02:00

347 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import collections
import datetime
import json
import logging
import os
import sys
# pipe handling
from signal import SIG_DFL, SIGPIPE, signal
from typing import Dict, List, TypedDict
import yaml
from config import Config
from documentation import Docs, interactive_doc_checker
from logo import LogoGenerator
from project import Project
from rom import ROMFile
from shuttle import ShuttleConfig
signal(SIGPIPE, SIG_DFL)
class Projects:
def __init__(self, config: Config, args):
self.args = args
self.config = config
self.project_dir = config["project_dir"]
if not os.path.exists(self.project_dir):
os.makedirs(self.project_dir)
only_projects = os.getenv("TT_ONLY_PROJECTS")
only_projects_list = only_projects.split(",") if only_projects else None
self.projects: List[Project] = []
project_list = [
entry
for entry in os.listdir(self.project_dir)
if os.path.isdir(os.path.join(self.project_dir, entry))
]
if args.test:
project_list = ["tt_um_chip_rom", "tt_um_factory_test"]
for index, project_id in enumerate(project_list):
project_dir = os.path.join(self.project_dir, project_id)
commit_id_file = os.path.join(project_dir, "commit_id.json")
if not os.path.exists(commit_id_file):
logging.warning(f"no commit_id.json in {project_dir}, skipping")
continue
commit_id_data = json.load(open(commit_id_file))
if commit_id_data.get("skip", False):
logging.warning(f"skipping {project_dir} (skip flag set)")
continue
if only_projects_list and project_id not in only_projects_list:
continue
project = Project(
index,
commit_id_data["repo"],
project_dir,
pdk=config["pdk"],
is_user_project=False,
)
project.commit_id = commit_id_data["commit"]
project.sort_id = commit_id_data["sort_id"]
# projects should now be installed, so load all the data from the yaml files
# fill projects will load from the fill project's directory
logging.debug("post clone setup")
project.post_clone_setup()
logging.debug(project)
if args.harden:
project.create_user_config()
project.golden_harden()
if args.update_shuttle:
project.check_ports(bool(config.get("powered_netlists", True)))
project.check_num_cells()
self.projects.append(project)
self.projects.sort(key=lambda x: x.sort_id)
all_macro_instances = [project.get_macro_name() for project in self.projects]
self.assert_unique(all_macro_instances)
all_gds_files = [project.get_macro_gds_filename() for project in self.projects]
self.assert_unique(all_gds_files)
logging.info(f"loaded {len(self.projects)} projects")
def assert_unique(self, check: List[str]):
duplicates = [
item for item, count in collections.Counter(check).items() if count > 1
]
if duplicates:
logging.error("duplicate projects: {}".format(duplicates))
exit(1)
def build_metrics(self):
total_seconds = 0.0
total_wire_length = 0
total_wires_count = 0
total_physical_cells = 0
max_cells = 0
min_cells = 1000
max_cell_project = None
max_util = 0.0
min_util = 100.0
max_util_project = None
languages: Dict[str, int] = {}
for project in self.projects:
try:
dt = datetime.datetime.strptime(
project.metrics["total_runtime"][:-3], "%Hh%Mm%Ss"
)
except KeyError:
continue
if project.is_chip_rom():
continue
delt = datetime.timedelta(
hours=dt.hour, minutes=dt.minute, seconds=dt.second
)
total_seconds += delt.total_seconds()
cell_count = project.get_cell_counts_from_gl()
script_dir = os.path.dirname(os.path.realpath(__file__))
with open(os.path.join(script_dir, "categories.json")) as fh:
categories = json.load(fh)
CategoryInfo = TypedDict(
"CategoryInfo", {"count": int, "examples": List[str]}
)
by_category: Dict[str, CategoryInfo] = {}
total = 0
for cell_name in cell_count:
cat_index = categories["map"][cell_name]
cat_name = categories["categories"][cat_index]
if cat_name in by_category:
by_category[cat_name]["count"] += cell_count[cell_name]
by_category[cat_name]["examples"].append(cell_name)
else:
by_category[cat_name] = {
"count": cell_count[cell_name],
"examples": [cell_name],
}
if cat_name not in ["Fill", "Tap", "Buffer", "Misc"]:
total += cell_count[cell_name]
if total < 10:
del by_category["Fill"]
del by_category["Tap"]
if "Buffer" in by_category:
del by_category["Buffer"]
print(project.get_macro_name(), total, by_category)
total_wire_length += int(project.metrics["wire_length"])
total_wires_count += int(project.metrics["wires_count"])
util = float(project.metrics["OpenDP_Util"])
num_cells = project.get_cell_count_from_synth()
total_physical_cells += num_cells
lang = project.info.language
if lang in languages:
languages[lang] += 1
else:
languages[lang] = 1
if num_cells > max_cells:
max_cells = num_cells
max_cell_project = project
if num_cells < min_cells:
min_cells = num_cells
if util > max_util:
max_util = util
max_util_project = project
if util < min_util:
min_util = util
logging.info(f"build time for all projects {total_seconds / 3600} hrs")
logging.info(f"total wire length {total_wire_length} um")
logging.info(f"total cells {total_physical_cells}")
logging.info(f"max cells {max_cells} for project {max_cell_project}")
logging.info(f"min cells {min_cells}")
logging.info(f"max util {max_util} for project {max_util_project}")
logging.info(f"min util {min_util}")
logging.info(f"languages {languages}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Tiny Tapeout configuration and docs")
with open("config.yaml") as fh:
config = yaml.safe_load(fh)
parser.add_argument(
"--list", help="list projects", action="store_const", const=True
)
parser.add_argument(
"--single", help="do action on single project", type=int, default=-1
)
parser.add_argument(
"--update-shuttle",
help="configure shuttle for build",
action="store_const",
const=True,
)
parser.add_argument(
"--copy-macros",
help="copy macros for building the tt_top project",
action="store_const",
const=True,
)
parser.add_argument(
"--copy-final-results",
help="copy final project files to gds/lef directories",
action="store_const",
const=True,
)
parser.add_argument(
"--create-foundry-submission",
help="create foundry_submission directory with the final GDS/OAS files",
action="store_const",
const=True,
)
parser.add_argument(
"--harden", help="harden project", action="store_const", const=True
)
parser.add_argument(
"--test", help="use test projects", action="store_const", const=True
)
parser.add_argument(
"--debug",
help="debug logging",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO,
)
parser.add_argument(
"--log-email",
help="print persons email in messages",
action="store_const",
const=True,
)
parser.add_argument(
"--update-image", help="update the image", action="store_const", const=True
)
parser.add_argument(
"--dump-json", help="dump json of all project data to given file"
)
parser.add_argument(
"--metrics", help="print some project metrics", action="store_const", const=True
)
parser.add_argument(
"--build-datasheet",
help="build datasheet using the typst template",
action="store_const",
const=True,
)
parser.add_argument("--doc-tapeout-index", help="path to json tapeout index")
parser.add_argument(
"--template-version",
help="set typst template version (default 1.0.0)",
default="1.0.0",
nargs="?",
)
parser.add_argument(
"--doc-check",
help="interactive project document checker",
action="store_const",
const=True,
)
args = parser.parse_args()
# setup log
log_format = logging.Formatter("%(asctime)s - %(levelname)-8s - %(message)s")
# configure the client logging
log = logging.getLogger("")
# has to be set to debug as is the root logger
log.setLevel(args.loglevel)
# create console handler and set level to info
ch = logging.StreamHandler(sys.stdout)
# create formatter for console
ch.setFormatter(log_format)
log.addHandler(ch)
projects = Projects(config, args)
if args.test:
modules_yaml_name = "modules.test.yaml"
else:
modules_yaml_name = "modules.yaml"
docs = Docs(config, projects.projects)
shuttle = ShuttleConfig(config, projects.projects, modules_yaml_name)
rom = ROMFile(config)
logo = LogoGenerator("tt", pdk=config["pdk"], config=config)
if args.list:
shuttle.list()
if args.metrics:
projects.build_metrics()
if args.update_shuttle:
shuttle.configure_mux()
rom.write_rom()
logo.gen_logo("top", "tt/logo/tt_logo_top.gds")
logo.gen_lef("top", "tt/logo/tt_logo_top.lef")
logo.gen_logo("bottom", "tt/logo/tt_logo_bottom.gds")
logo.gen_lef("bottom", "tt/logo/tt_logo_bottom.lef")
if not args.test:
docs.build_index()
if args.copy_macros:
shuttle.copy_macros()
if args.copy_final_results:
shuttle.copy_final_results()
if args.create_foundry_submission:
shuttle.create_foundry_submission("foundry_submission")
if args.update_image:
docs.update_image()
if args.build_datasheet:
shuttle.configure_mux()
docs.build_datasheet(args.template_version, args.doc_tapeout_index)
if args.doc_check:
interactive_doc_checker()