|  | #!/usr/bin/env python3 | 
|  | # Copyright lowRISC contributors. | 
|  | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | # SPDX-License-Identifier: Apache-2.0 | 
|  | """make_new_dif.py is a script for instantiating templates needed to begin | 
|  | development on a new DIF. | 
|  |  | 
|  | To instantiate the files for a new IP named my_ip, run the command | 
|  | $ util/make_new_dif.py --ip my_ip --peripheral "my peripheral" | 
|  | where "my peripheral" is a documentation-friendly name for your peripheral. | 
|  | Compare "pwrmgr" and "power manager". | 
|  |  | 
|  | It will instantiate: | 
|  | - `sw/device/lib/dif/dif_template.h.tpl` as the DIF Header (into dif_<ip>.h). | 
|  | - `sw/device/lib/dif/templates/dif_autogen.h.tpl` as the autogenerated DIF | 
|  | Header (into dif_<ip>_autogen.h). | 
|  | - `doc/project/sw_checklist.md.tpl` as the DIF Checklist (into dif_<ip>.md). | 
|  |  | 
|  | See both templates for more information. | 
|  |  | 
|  | You can also use the `--only=header`, `--only=autogen`, `--only=checklist` to | 
|  | instantiate a subset of the templates. This can be passed multiple times, and | 
|  | including `--only=all` will instantiate every template. | 
|  |  | 
|  | Note: the non-autogenerated files will still need some cleaning up before they | 
|  | can be used. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import glob | 
|  | import logging | 
|  | import shutil | 
|  | import subprocess | 
|  | import sys | 
|  | from collections import OrderedDict | 
|  | from pathlib import Path | 
|  |  | 
|  | import hjson | 
|  | from mako.template import Template | 
|  |  | 
|  | # This file is $REPO_TOP/util/make_new_dif.py, so it takes two parent() | 
|  | # calls to get back to the top. | 
|  | REPO_TOP = Path(__file__).resolve().parent.parent | 
|  |  | 
|  | ALL_PARTS = ["header", "checklist", "autogen"] | 
|  |  | 
|  | # Subset of IPs for which some portion of the DIFs have been auto-generated. | 
|  | # NOTE: This exists while autogenerated DIF code is checked into the repository | 
|  | # to warn those who update the templates to re-generate the code. Also, as more | 
|  | # auto-generated DIF code is checked in (see | 
|  | # https://github.com/lowRISC/opentitan/issues/8142), this list will expand. | 
|  | ALL_AUTOGEN_IPS = ["uart"] | 
|  |  | 
|  |  | 
|  | class Irq: | 
|  | """Holds IRQ information for populating DIF code templates. | 
|  |  | 
|  | Attributes: | 
|  | name_snake (str): IRQ short name in lower snake case. | 
|  | name_upper (str): IRQ short name in upper snake case. | 
|  | name_camel (str): IRQ short name in camel case. | 
|  | description (str): full description of the IRQ. | 
|  |  | 
|  | """ | 
|  | def __init__(self, irq: OrderedDict) -> None: | 
|  | self.name_snake = irq["name"] | 
|  | self.name_upper = self.name_snake.upper() | 
|  | self.name_camel = "".join( | 
|  | [word.capitalize() for word in self.name_snake.split("_")]) | 
|  | _multiline_description = irq["desc"][0].upper() + irq["desc"][1:] | 
|  | self.description = _multiline_description.replace("\n", " ") | 
|  | self.width = irq["width"] if "width" in irq else 1 | 
|  |  | 
|  |  | 
|  | class Ip: | 
|  | """Holds all IP metadata mined from an IP's name and HJSON file. | 
|  |  | 
|  | Attributes: | 
|  | name_snake (str): IP short name in lower snake case. | 
|  | name_upper (str): IP short name in upper snake case. | 
|  | name_camel (str): IP short name in camel case. | 
|  | name_long_lower (str): IP full name in lower case. | 
|  | name_long_upper (str): IP full name with first letter capitalized. | 
|  | hjson_data (OrderedDict): IP metadata from hw/ip/<ip>/data/<ip>.hjson. | 
|  | irqs (List[Irq]): List of Irq objects constructed from hjson_data. | 
|  |  | 
|  | """ | 
|  | def __init__(self, name_snake: str, name_long_lower: str) -> None: | 
|  | """Mines metadata to populate this Ip object. | 
|  |  | 
|  | Args: | 
|  | name_snake: IP short name in lower snake case (e.g., pwrmgr). | 
|  | name_long_lower: IP full name in lower case (e.g., power manager). | 
|  | """ | 
|  | # Generate various IP name formats. | 
|  | self.name_snake = name_snake | 
|  | self.name_upper = self.name_snake.upper() | 
|  | self.name_camel = "".join( | 
|  | [word.capitalize() for word in self.name_snake.split("_")]) | 
|  | self.name_long_lower = name_long_lower | 
|  | # We just want to set the first character to title case. In particular, | 
|  | # .capitalize() does not do the right thing, since it would convert | 
|  | # UART to Uart. | 
|  | self.name_long_upper = self.name_long_lower[0].upper( | 
|  | ) + self.name_long_lower[1:] | 
|  | # Load HJSON data. | 
|  | _hjson_file = REPO_TOP / "hw/ip/{0}/data/{0}.hjson".format( | 
|  | self.name_snake) | 
|  | with _hjson_file.open("r") as f: | 
|  | _hjson_str = f.read() | 
|  | self.hjson_data = hjson.loads(_hjson_str) | 
|  | # Load IRQ data from HJSON. | 
|  | self.irqs = self._load_irqs() | 
|  |  | 
|  | def _load_irqs(self): | 
|  | assert (self.hjson_data and | 
|  | "ERROR: must load IP HJSON before loarding IRQs") | 
|  | irqs = [] | 
|  | if "interrupt_list" in self.hjson_data: | 
|  | for irq in self.hjson_data["interrupt_list"]: | 
|  | irqs.append(Irq(irq)) | 
|  | return irqs | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | dif_dir = REPO_TOP / "sw/device/lib/dif" | 
|  | autogen_dif_dir = dif_dir / "autogen" | 
|  |  | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument( | 
|  | "--mode", | 
|  | "-m", | 
|  | choices=["single", "batch"], | 
|  | default="single", | 
|  | required=True, | 
|  | help="mode to generate DIF code; use 'single' if no DIF code exists.") | 
|  | parser.add_argument("--ip", | 
|  | "-i", | 
|  | help="the short name of the IP, in snake_case.") | 
|  | parser.add_argument("--peripheral", | 
|  | "-p", | 
|  | help="the documentation-friendly name of the IP.") | 
|  | parser.add_argument("--only", | 
|  | choices=ALL_PARTS, | 
|  | default=[], | 
|  | action="append", | 
|  | help="only create some files; defaults to all.") | 
|  | args = parser.parse_args() | 
|  |  | 
|  | # Parse CMD line args. | 
|  | ips = [] | 
|  |  | 
|  | # Check for batch generation mode (used in CI check: | 
|  | # ci/scripts/check-generated.sh") | 
|  | if args.mode == "batch": | 
|  | if len(args.only) != 1 or args.only[0] != "autogen": | 
|  | raise RuntimeError( | 
|  | "can only batch generate completely auto-generated code.") | 
|  | # Create list of IPs to re-generate DIF code for. | 
|  | for autogen_src_filename in glob.iglob( | 
|  | str(REPO_TOP / "sw/device/lib/dif/autogen/*.c")): | 
|  | # NOTE: the line below takes as input a file path | 
|  | # (/path/to/dif_uart_autogen.c) and returns the IP name in lower | 
|  | # case snake mode (i.e., uart). | 
|  | ip_name_snake = Path(autogen_src_filename).stem[4:-8] | 
|  | # NOTE: ip.name_long_* not needed for auto-generated files which | 
|  | # are the only files (re-)generated in batch mode. | 
|  | ips.append(Ip(ip_name_snake, "AUTOGEN")) | 
|  | else: | 
|  | assert ( | 
|  | args.ip and args.peripheral and | 
|  | "ERROR: must pass --ip and --peripheral options in 'single' mode.") | 
|  | ips.append(Ip(args.ip, args.peripheral)) | 
|  |  | 
|  | # Default to generating all parts. | 
|  | if len(args.only) == 0: | 
|  | args.only += ALL_PARTS | 
|  |  | 
|  | # Create output directories if needed. | 
|  | if len(args.only) > 0: | 
|  | dif_dir.mkdir(exist_ok=True) | 
|  | autogen_dif_dir.mkdir(exist_ok=True) | 
|  |  | 
|  | for ip in ips: | 
|  | if "header" in args.only: | 
|  | header_template_file = (REPO_TOP / | 
|  | "util/make_new_dif/dif_template.h.tpl") | 
|  | header_template = Template(header_template_file.read_text()) | 
|  | header_out_file = dif_dir / "dif_{}.h".format(ip.name_snake) | 
|  | header_out_file.write_text(header_template.render(ip=ip)) | 
|  | print("DIF header successfully written to {}.".format( | 
|  | str(header_out_file))) | 
|  |  | 
|  | if "autogen" in args.only: | 
|  | # Render all templates | 
|  | for filetype in [".h", ".c", "_unittest.cc"]: | 
|  | # Build input/output file names. | 
|  | template_file = ( | 
|  | REPO_TOP / f"util/make_new_dif/dif_autogen{filetype}.tpl") | 
|  | out_file = (autogen_dif_dir / | 
|  | f"dif_{ip.name_snake}_autogen{filetype}") | 
|  |  | 
|  | # Read in template. | 
|  | template = Template(template_file.read_text(), | 
|  | strict_undefined=True) | 
|  |  | 
|  | # Generate output file. | 
|  | out_file.write_text(template.render(ip=ip, irqs=ip.irqs)) | 
|  |  | 
|  | # Format autogenerated file with clang-format. | 
|  | assert (shutil.which("clang-format") and | 
|  | "ERROR: clang-format must be installed to format " | 
|  | " autogenerated code to pass OpenTitan CI checks.") | 
|  | try: | 
|  | subprocess.check_call(["clang-format", "-i", out_file]) | 
|  | except subprocess.CalledProcessError: | 
|  | logging.error( | 
|  | f"failed to format {out_file} with clang-format.") | 
|  | sys.exit(1) | 
|  |  | 
|  | print("Autogenerated DIF successfully written to {}.".format( | 
|  | out_file)) | 
|  |  | 
|  | if "checklist" in args.only: | 
|  | checklist_template_file = REPO_TOP / "doc/project/sw_checklist.md.tpl" | 
|  | markdown_template = Template(checklist_template_file.read_text()) | 
|  | checklist_out_file = dif_dir / "dif_{}.md".format(ip.name_snake) | 
|  | checklist_out_file.write_text(markdown_template.render(ip=ip)) | 
|  | print("DIF Checklist successfully written to {}.".format( | 
|  | str(checklist_out_file))) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |