[dif] added templates/tooling to autogenerate IRQ DIFs
This partially addresses #8142. Specifically this commit:
- Adds three template files (*.h, *.c, and *_unittest.cc) to enable
autogenerating IRQ DIFs across all IPs.
- Adds support to util/make_new_dif.py to populate the above added templates
with data parsed from each IP's HJSON configuration file and its name.
- Removes IRQ functions from existing DIF header template (since these
will be fully autogenerated.
Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/util/make_new_dif.py b/util/make_new_dif.py
index 7f9f48f..85294da 100755
--- a/util/make_new_dif.py
+++ b/util/make_new_dif.py
@@ -2,126 +2,217 @@
# 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.
-# 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).
-# - `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` or `--only=checklist` to instantiate a
-# subset of the templates. This can be passed multiple times, and including
-# `--only=all` will instantiate every template.
-#
-# The produced files will still need some cleaning up before they can be used.
+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 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"]
+ALL_PARTS = ["header", "checklist", "autogen"]
+
+
+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", " ")
+
+
+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 = []
+ for irq in self.hjson_data["interrupt_list"]:
+ irqs.append(Irq(irq))
+ return irqs
def main():
- dif_dir = REPO_TOP / 'sw/device/lib/dif'
+ dif_dir = REPO_TOP / "sw/device/lib/dif"
+ autogen_dif_dir = dif_dir / "autogen"
parser = argparse.ArgumentParser()
- parser.add_argument('--ip',
- '-i',
+ parser.add_argument("--ip",
+ "-i",
required=True,
- help='the short name of the IP, in snake_case')
- parser.add_argument('--peripheral',
- '-p',
+ help="the short name of the IP, in snake_case")
+ parser.add_argument("--peripheral",
+ "-p",
required=True,
- help='the documentation-friendly name of the IP')
- parser.add_argument(
- '--handle-param',
- '-a',
- default='handle',
- help='an optional name to replace the `handle` parameter name')
- parser.add_argument('--only',
- choices=['all'] + ALL_PARTS,
+ 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.')
- parser.add_argument('--output',
- '-o',
- type=Path,
- default=dif_dir,
- help='directory to place the output files into.')
+ action="append",
+ help="only create some files; defaults to all.")
args = parser.parse_args()
-
+ # Parse CMD line args.
+ ip = Ip(args.ip, args.peripheral)
+ # Default to generating all parts.
if len(args.only) == 0:
- args.only += ['all']
- if 'all' in args.only:
args.only += ALL_PARTS
- ip_snake = args.ip
- ip_camel = ''.join([word.capitalize() for word in args.ip.split('_')])
- ip_upper = ip_snake.upper()
- periph_lower = args.peripheral
- # 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.
- periph_upper = periph_lower[0].upper() + periph_lower[1:]
- handle = args.handle_param
-
+ # Create output directories if needed.
if len(args.only) > 0:
- args.output.mkdir(exist_ok=True)
+ dif_dir.mkdir(exist_ok=True)
+ autogen_dif_dir.mkdir(exist_ok=True)
if "header" in args.only:
- header_template_file = args.output / 'dif_template.h.tpl'
+ header_template_file = (REPO_TOP /
+ "util/make_new_dif/dif_template.h.tpl")
- with header_template_file.open('r') as f:
+ with header_template_file.open("r") as f:
header_template = Template(f.read())
- header_out_file = dif_dir / 'dif_{}.h'.format(ip_snake)
- with header_out_file.open('w') as f:
- f.write(
- header_template.render(
- ip_snake=ip_snake,
- ip_camel=ip_camel,
- ip_upper=ip_upper,
- periph_lower=periph_lower,
- periph_upper=periph_upper,
- handle=handle,
- ))
+ header_out_file = dif_dir / "dif_{}.h".format(ip.name_snake)
+ with header_out_file.open("w") as f:
+ f.write(header_template.render(ip=ip))
- print('DIF header successfully written to {}.'.format(
+ print("DIF header successfully written to {}.".format(
str(header_out_file)))
- if "checklist" in args.only:
- checklist_template_file = REPO_TOP / 'doc/project/sw_checklist.md.tpl'
+ if "autogen" in args.only:
+ # Render all templates
+ for filetype in ["inc", "c", "unittest"]:
+ assert (ip.irqs and "ERROR: this IP generates no interrupts.")
+ # Build input/output file names.
+ if filetype == "unittest":
+ template_file = (
+ REPO_TOP /
+ f"util/make_new_dif/dif_autogen_{filetype}.cc.tpl")
+ out_file = (autogen_dif_dir /
+ f"dif_{ip.name_snake}_autogen_unittest.cc")
+ else:
+ 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}")
- with checklist_template_file.open('r') as f:
- markdown_template = Template(f.read())
+ # Read in template.
+ with template_file.open("r") as f:
+ template = Template(f.read())
- checklist_out_file = args.output / 'dif_{}.md'.format(ip_snake)
- with checklist_out_file.open('w') as f:
- f.write(
- markdown_template.render(
- ip_name=ip_snake,
- dif_name=ip_snake,
- display_name=periph_upper,
+ # Generate output file.
+ with out_file.open("w") as f:
+ f.write(template.render(
+ ip=ip,
+ irqs=ip.irqs,
))
- print('DIF Checklist successfully written to {}.'.format(
+ # 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"
+
+ with checklist_template_file.open("r") as f:
+ markdown_template = Template(f.read())
+
+ checklist_out_file = dif_dir / "dif_{}.md".format(ip.name_snake)
+ with checklist_out_file.open("w") as f:
+ f.write(markdown_template.render(ip=ip))
+
+ print("DIF Checklist successfully written to {}.".format(
str(checklist_out_file)))
-if __name__ == '__main__':
+if __name__ == "__main__":
main()