| #!/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 ip_ctrl, run the command: | |
| $ util/make_new_dif.py --ip-name-snake "ip_ctrl" --ip-name-long "IP Controller" | |
| where "IP Controller" is a documentation-friendly name for the IP. | |
| For example, compare "pwrmgr" and "Power Manager". | |
| It will instantiate: | |
| - `sw/device/lib/dif/dif_template.h.tpl` as the DIF Header boilerplate | |
| (into `dif_<ip>.h`), which should be manually edited/enhanced. | |
| - `sw/device/lib/dif/templates/dif_autogen.h.tpl` as the autogenerated DIF | |
| Header (into `dif_<ip>_autogen.h`). | |
| - `sw/device/lib/dif/templates/dif_autogen.c.tpl` as the autogenerated DIF | |
| implementation (into `dif_<ip>_autogen.c`). | |
| - `sw/device/lib/dif/templates/dif_autogen_unittest.cc.tpl` as the | |
| autogenerated DIF unit tests (into `dif_<ip>_autogen_unittest.cc`). | |
| - `doc/project/sw_checklist.md.tpl` as the DIF Checklist (into dif_<ip>.md), | |
| which should be manually edited. | |
| 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 subprocess | |
| import sys | |
| from pathlib import Path | |
| import hjson | |
| from mako.template import Template | |
| import topgen.lib as lib | |
| from autogen_banner import get_autogen_banner | |
| from autogen_testutils.gen import gen_testutils | |
| from make_new_dif.ip import Ip | |
| # 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"] | |
| 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=["new", "regen"], | |
| default="new", | |
| required=True, | |
| help="mode to generate DIF code. Use 'new' if no DIF code exists." | |
| "Use 'regen' to regenerate all auto-generated DIFs for all IPs.") | |
| parser.add_argument("--topcfg", "-t", help="path of the top hjson file.") | |
| parser.add_argument("--ip-name-snake", | |
| "-i", | |
| help="the short name of the IP, in snake_case.") | |
| parser.add_argument("--ip-name-long", | |
| "-l", | |
| 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 = [] | |
| # Parse toplevel Hjson to get IPs that are templated / generated with IPgen. | |
| topcfg_path = REPO_TOP / "hw/top_earlgrey/data/top_earlgrey.hjson" | |
| if args.topcfg: | |
| topcfg_path = args.topcfg | |
| try: | |
| with open(topcfg_path, 'r') as ftop: | |
| topcfg = hjson.load(ftop, use_decimal=True) | |
| except FileNotFoundError: | |
| print(f"hjson {topcfg_path} could not be found") | |
| sys.exit(1) | |
| templated_modules = lib.get_templated_modules(topcfg) | |
| ipgen_modules = lib.get_ipgen_modules(topcfg) | |
| reggen_top_modules = lib.get_top_reggen_modules(topcfg) | |
| # Check for regeneration mode (used in CI check: | |
| # ci/scripts/check-generated.sh) | |
| if args.mode == "regen": | |
| if len(args.only) != 1 or args.only[0] != "autogen": | |
| raise RuntimeError( | |
| "can only regenerate DIF code that is auto-generated.") | |
| # 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 regen mode. | |
| ips.append( | |
| Ip(ip_name_snake, "AUTOGEN", templated_modules, ipgen_modules, | |
| reggen_top_modules)) | |
| else: | |
| assert args.ip_name_snake and args.ip_name_long, \ | |
| "ERROR: pass --ip-name-snake and --ip-name-long when --mode=new." | |
| ips.append( | |
| Ip(args.ip_name_snake, args.ip_name_long, templated_modules, | |
| ipgen_modules, reggen_top_modules)) | |
| # 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) | |
| # Render DIF templates. | |
| for ip in ips: | |
| if "header" in args.only: | |
| header_template_file = ( | |
| REPO_TOP / "util/make_new_dif/templates/dif_template.h.tpl") | |
| header_out_file = dif_dir / "dif_{}.h".format(ip.name_snake) | |
| if header_out_file.is_file(): | |
| raise FileExistsError( | |
| "DIF header already exists for the IP. To overwrite, " | |
| "delete existing header and try again.") | |
| header_template = Template(header_template_file.read_text()) | |
| 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: | |
| out_files = [] | |
| # Render all templates | |
| for filetype in [".h", ".c", "_unittest.cc"]: | |
| # Build input/output file names. | |
| template_file = ( | |
| REPO_TOP / | |
| f"util/make_new_dif/templates/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, | |
| autogen_banner=get_autogen_banner( | |
| "util/make_new_dif.py --mode=regen --only=autogen", | |
| "//"))) | |
| # Assemble a list of files to format all at once | |
| out_files += [out_file] | |
| # Format autogenerated file with clang-format. | |
| try: | |
| subprocess.check_call( | |
| ["./bazelisk.sh", "run", "//quality:clang_format_fix", "--"] + out_files | |
| ) | |
| 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_files)) | |
| if "checklist" in args.only: | |
| checklist_template_file = REPO_TOP / "doc/project/sw_checklist.md.tpl" | |
| checklist_out_file = dif_dir / "dif_{}.md".format(ip.name_snake) | |
| if checklist_out_file.is_file(): | |
| raise FileExistsError( | |
| "DIF checklist already exists for the IP. To " | |
| "overwrite, delete existing checklist and try again.") | |
| markdown_template = Template(checklist_template_file.read_text()) | |
| checklist_out_file.write_text(markdown_template.render(ip=ip)) | |
| print("DIF Checklist successfully written to {}.".format( | |
| str(checklist_out_file))) | |
| # Render testutils templates. | |
| if args.mode == "regen" or "autogen" in args.only: | |
| gen_testutils(ips) | |
| if __name__ == "__main__": | |
| main() |