[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()
diff --git a/util/make_new_dif/dif_autogen.c.tpl b/util/make_new_dif/dif_autogen.c.tpl
new file mode 100644
index 0000000..c426364
--- /dev/null
+++ b/util/make_new_dif/dif_autogen.c.tpl
@@ -0,0 +1,210 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+<%doc>
+    This file is the "auto-generated DIF library implementation template", which
+    provides implementations of some mandatory DIFs that are similar across all
+    IPs. When rendered, this template implements the DIFs defined in the
+    auto-generated DIF header file (see util/make_new_dif/dif_autogen.inc.tpl).
+
+    Note, this template requires the following Python objects to be passed:
+
+    1. ip: See util/make_new_dif.py for the definition of the `ip` obj.
+    2. list[irq]: See util/make_new_dif.py for the definition of the `irq` obj.
+</%doc>
+
+<%def name="mmio_region_read32(intr_reg_upper)">mmio_region_read32(
+  ${ip.name_snake}->base_addr, 
+  ${ip.name_upper}_INTR_${intr_reg_upper}_REG_OFFSET);
+</%def>
+
+<%def name="mmio_region_write32(intr_reg_upper, value)">mmio_region_write32(
+  ${ip.name_snake}->base_addr, 
+  ${ip.name_upper}_INTR_${intr_reg_upper}_REG_OFFSET,
+  ${value});
+</%def>
+
+// This file is auto-generated.
+
+#include "sw/device/lib/dif/dif_${ip.name_snake}.h"
+
+#include "${ip.name_snake}_regs.h"  // Generated.
+
+/**
+ * Get the corresponding interrupt register bit offset. INTR_STATE,
+ * INTR_ENABLE and INTR_TEST registers have the same bit offsets, so this
+ * routine can be reused.
+ */
+static bool ${ip.name_snake}_get_irq_bit_index(
+  dif_${ip.name_snake}_irq_t irq,
+  bitfield_bit32_index_t *index_out) {
+
+  switch (irq) {
+% for irq in irqs:
+    case kDif${ip.name_camel}Irq${irq.name_camel}:
+      *index_out = ${ip.name_upper}_INTR_STATE_${irq.name_upper}_BIT;
+      break;
+% endfor
+    default:
+      return false;
+  }
+
+  return true;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_get_state(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_state_snapshot_t *snapshot) {
+
+  if (${ip.name_snake} == NULL || snapshot == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  *snapshot = ${mmio_region_read32("STATE")}
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_is_pending(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq,
+  bool *is_pending) {
+
+  if (${ip.name_snake} == NULL || is_pending == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  bitfield_bit32_index_t index;
+  if (!${ip.name_snake}_get_irq_bit_index(irq, &index)) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  uint32_t intr_state_reg = ${mmio_region_read32("STATE")}
+  *is_pending = bitfield_bit32_read(intr_state_reg, index);
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_acknowledge(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq) {
+
+  if (${ip.name_snake} == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  bitfield_bit32_index_t index;
+  if (!${ip.name_snake}_get_irq_bit_index(irq, &index)) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  // Writing to the register clears the corresponding bits (Write-one clear).
+  uint32_t intr_state_reg = bitfield_bit32_write(0, index, true);
+  ${mmio_region_write32("STATE", "intr_state_reg")}
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_get_enabled(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq,
+  dif_${ip.name_snake}_toggle_t *state) {
+  
+  if (${ip.name_snake} == NULL || state == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  bitfield_bit32_index_t index;
+  if (!${ip.name_snake}_get_irq_bit_index(irq, &index)) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  uint32_t intr_enable_reg = ${mmio_region_read32("ENABLE")}
+  bool is_enabled = bitfield_bit32_read(intr_enable_reg, index);
+  *state = is_enabled ? 
+    kDif${ip.name_camel}ToggleEnabled : kDif${ip.name_camel}ToggleDisabled;
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_set_enabled(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq,
+  dif_${ip.name_snake}_toggle_t state) {
+
+  if (${ip.name_snake} == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  bitfield_bit32_index_t index;
+  if (!${ip.name_snake}_get_irq_bit_index(irq, &index)) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  uint32_t intr_enable_reg = ${mmio_region_read32("ENABLE")}
+  bool enable_bit = (state == kDifUartToggleEnabled) ? true : false;
+  intr_enable_reg = bitfield_bit32_write(intr_enable_reg, index, enable_bit);
+  ${mmio_region_write32("ENABLE", "intr_enable_reg")}
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_force(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq) {
+
+  if (${ip.name_snake} == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  bitfield_bit32_index_t index;
+  if (!${ip.name_snake}_get_irq_bit_index(irq, &index)) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  uint32_t intr_test_reg = bitfield_bit32_write(0, index, true);
+  ${mmio_region_write32("TEST", "intr_test_reg")}
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_disable_all(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_enable_snapshot_t *snapshot) {
+
+  if (${ip.name_snake} == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  // Pass the current interrupt state to the caller, if requested.
+  if (snapshot != NULL) {
+    *snapshot = ${mmio_region_read32("ENABLE")}
+  }
+
+  // Disable all interrupts.
+  ${mmio_region_write32("ENABLE", "0u")}
+
+  return kDif${ip.name_camel}Ok;
+}
+
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_restore_all(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  const dif_${ip.name_snake}_irq_enable_snapshot_t *snapshot) {
+
+  if (${ip.name_snake} == NULL || snapshot == NULL) {
+    return kDif${ip.name_camel}BadArg;
+  }
+
+  ${mmio_region_write32("ENABLE", "*snapshot")}
+
+  return kDif${ip.name_camel}Ok;
+}
diff --git a/util/make_new_dif/dif_autogen.inc.tpl b/util/make_new_dif/dif_autogen.inc.tpl
new file mode 100644
index 0000000..628b614
--- /dev/null
+++ b/util/make_new_dif/dif_autogen.inc.tpl
@@ -0,0 +1,234 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+<%doc>
+    This file is the "auto-generated DIF library header template", which
+    provides some mandatory DIFs prototypes, that are similar across all IPs.
+    This template is rendered into a .inc file that is included by the semi-
+    auto-generated DIF header file (see util/make_new_dif/dif_template.h.tpl).
+    Note, since this template is designed to manifest as a non-standalone header
+    it has the file extension, .inc.
+
+    For more information, see: https://github.com/lowRISC/opentitan/issues/8142
+
+    Note, this template requires the following Python objects to be passed:
+
+    1. ip: See util/make_new_dif.py for the definition of the `ip` obj.
+    2. list[irq]: See util/make_new_dif.py for the definition of the `irq` obj.
+</%doc>
+
+// This file is auto-generated.
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_AUTOGEN_DIF_${ip.name_upper}_AUTOGEN_INC_
+#define OPENTITAN_SW_DEVICE_LIB_DIF_AUTOGEN_DIF_${ip.name_upper}_AUTOGEN_INC_
+
+/**
+ * @file
+ * @brief <a href="/hw/ip/${ip.name_snake}/doc/">${ip.name_upper}</a> Device Interface Functions
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "sw/device/lib/base/bitfield.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_warn_unused_result.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * A handle to ${ip.name_long_lower}.
+ *
+ * This type should be treated as opaque by users.
+ */
+typedef struct dif_${ip.name_snake} { 
+  /**
+   * The base address for the ${ip.name_upper} hardware registers.
+   */
+  mmio_region_t base_addr;
+} dif_${ip.name_snake}_t;
+
+/**
+ * The result of a ${ip.name_long_lower} operation.
+ *
+ * NOTE: additional result values can be defined in the manually-implemented
+ * header.
+ */
+typedef enum dif_${ip.name_snake}_result {
+  /**
+   * Indicates that the operation succeeded.
+   */
+  kDif${ip.name_camel}Ok = 0,
+  /**
+   * Indicates some unspecified failure.
+   */
+  kDif${ip.name_camel}Error = 1,
+  /**
+   * Indicates that some parameter passed into a function failed a
+   * precondition.
+   *
+   * When this value is returned, no hardware operations occurred.
+   */
+  kDif${ip.name_camel}BadArg = 2,
+} dif_${ip.name_snake}_result_t;
+
+/**
+ * A toggle state: enabled, or disabled.
+ *
+ * This enum may be used instead of a `bool` when describing an enabled/disabled
+ * state.
+ */
+typedef enum dif_${ip.name_snake}_toggle {
+  /**
+   * The "enabled" state.
+   */
+  kDif${ip.name_camel}ToggleEnabled,
+  /**
+   * The "disabled" state.
+   */
+  kDif${ip.name_camel}ToggleDisabled,
+} dif_${ip.name_snake}_toggle_t;
+
+/**
+ * A ${ip.name_long_lower} interrupt request type.
+ */
+typedef enum dif_${ip.name_snake}_irq {
+% for irq in irqs:
+  /**
+   * ${irq.description}
+   */
+  kDif${ip.name_camel}Irq${irq.name_camel} = ${loop.index},
+% endfor
+} dif_${ip.name_snake}_irq_t;
+
+/**
+ * A snapshot of the state of the interrupts for this IP.
+ *
+ * This is an opaque type, to be used with the `dif_${ip.name_snake}_irq_get_state()`
+ * function.
+ */
+typedef uint32_t dif_${ip.name_snake}_irq_state_snapshot_t;
+
+/**
+ * A snapshot of the enablement state of the interrupts for this IP.
+ *
+ * This is an opaque type, to be used with the
+ * `dif_${ip.name_snake}_irq_disable_all()` and `dif_${ip.name_snake}_irq_restore_all()`
+ * functions.
+ */
+typedef uint32_t dif_${ip.name_snake}_irq_enable_snapshot_t;
+
+/**
+ * Returns whether a particular interrupt is currently pending.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param irq An interrupt request.
+ * @param[out] is_pending Out-param for whether the interrupt is pending.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_get_state(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_state_snapshot_t *snapshot);
+
+/**
+ * Returns whether a particular interrupt is currently pending.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param irq An interrupt request.
+ * @param[out] is_pending Out-param for whether the interrupt is pending.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_is_pending(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq,
+  bool *is_pending);
+
+/**
+ * Acknowledges a particular interrupt, indicating to the hardware that it has
+ * been successfully serviced.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param irq An interrupt request.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_acknowledge(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq);
+
+/**
+ * Checks whether a particular interrupt is currently enabled or disabled.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param irq An interrupt request.
+ * @param[out] state Out-param toggle state of the interrupt.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_get_enabled(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq,
+  dif_${ip.name_snake}_toggle_t *state);
+
+/**
+ * Sets whether a particular interrupt is currently enabled or disabled.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param irq An interrupt request.
+ * @param state The new toggle state for the interrupt.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_set_enabled(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq,
+  dif_${ip.name_snake}_toggle_t state);
+
+/**
+ * Forces a particular interrupt, causing it to be serviced as if hardware had
+ * asserted it.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param irq An interrupt request.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_force(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_t irq);
+
+/**
+ * Disables all interrupts, optionally snapshotting all enable states for later
+ * restoration.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param[out] snapshot Out-param for the snapshot; may be `NULL`.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_disable_all(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_irq_enable_snapshot_t *snapshot);
+
+/**
+ * Restores interrupts from the given (enable) snapshot.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param snapshot A snapshot to restore from.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_irq_restore_all(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  const dif_${ip.name_snake}_irq_enable_snapshot_t *snapshot);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif  // OPENTITAN_SW_DEVICE_LIB_DIF_AUTOGEN_DIF_${ip.name_upper}_AUTOGEN_INC_
diff --git a/util/make_new_dif/dif_autogen_unittest.cc.tpl b/util/make_new_dif/dif_autogen_unittest.cc.tpl
new file mode 100644
index 0000000..b1a3ec0
--- /dev/null
+++ b/util/make_new_dif/dif_autogen_unittest.cc.tpl
@@ -0,0 +1,402 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+<%doc>
+    This file is the "auto-generated DIF library unit test template", which
+    provides implementations of unit tests for some mandatory DIFs that are
+    similar across all IPs. When rendered, this template implements unit tests
+    for the DIFs defined in the auto-generated DIF header file (see
+    util/make_new_dif/dif_autogen.inc.tpl).
+
+    This template requires the following Python objects to be passed:
+
+    1. ip: See util/make_new_dif.py for the definition of the `ip` obj.
+    2. list[irq]: See util/make_new_dif.py for the definition of the `irq` obj.
+</%doc>
+
+// This file is auto-generated.
+
+#include "sw/device/lib/dif/dif_${ip.name_snake}_unittest.h"
+
+namespace dif_${ip.name_snake}_unittest {
+namespace {
+using testing::Eq;
+using testing::Test;
+
+class IrqGetStateTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqGetStateTest, NullArgs) {
+  dif_${ip.name_snake}_irq_state_snapshot_t irq_snapshot;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_state(
+      nullptr, 
+      &irq_snapshot),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_state(
+      &${ip.name_snake}_, 
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_state(
+      nullptr, 
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqGetStateTest, SuccessAllRaised) {
+  dif_${ip.name_snake}_irq_state_snapshot_t irq_snapshot;
+
+  EXPECT_READ32(${ip.name_upper}_INTR_STATE_REG_OFFSET, 
+    std::numeric_limits<uint32_t>::max());
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_state(
+      &${ip.name_snake}_, 
+      &irq_snapshot),
+    kDif${ip.name_camel}Ok);
+  EXPECT_EQ(irq_snapshot, std::numeric_limits<uint32_t>::max());
+}
+
+TEST_F(IrqGetStateTest, SuccessNoneRaised) {
+  dif_${ip.name_snake}_irq_state_snapshot_t irq_snapshot;
+
+  EXPECT_READ32(${ip.name_upper}_INTR_STATE_REG_OFFSET, 0);
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_state(
+      &${ip.name_snake}_, 
+      &irq_snapshot),
+    kDif${ip.name_camel}Ok);
+  EXPECT_EQ(irq_snapshot, 0);
+}
+
+class IrqIsPendingTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqIsPendingTest, NullArgs) {
+  bool is_pending;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_is_pending(
+      nullptr, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      &is_pending),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_is_pending(
+      &${ip.name_snake}_, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_is_pending(
+      nullptr,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqIsPendingTest, BadIrq) {
+  bool is_pending;
+  // All interrupt CSRs are 32 bit so interrupt 32 will be invalid.
+  EXPECT_EQ(dif_${ip.name_snake}_irq_is_pending(
+      &${ip.name_snake}_, 
+      (dif_${ip.name_snake}_irq_t)32,
+      &is_pending),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqIsPendingTest, Success) {
+  bool irq_state;
+
+  // Get the first IRQ state.
+  irq_state = false;
+  EXPECT_READ32(${ip.name_upper}_INTR_STATE_REG_OFFSET,
+                {{${ip.name_upper}_INTR_STATE_${irqs[0].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_is_pending(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      &irq_state),
+    kDif${ip.name_camel}Ok);
+  EXPECT_TRUE(irq_state);
+
+  // Get the last IRQ state.
+  irq_state = true;
+  EXPECT_READ32(${ip.name_upper}_INTR_STATE_REG_OFFSET,
+                {{${ip.name_upper}_INTR_STATE_${irqs[-1].name_upper}_BIT, false}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_is_pending(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[-1].name_camel},
+      &irq_state),
+    kDif${ip.name_camel}Ok);
+  EXPECT_FALSE(irq_state);
+}
+
+class IrqAcknowledgeTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqAcknowledgeTest, NullArgs) {
+  EXPECT_EQ(dif_${ip.name_snake}_irq_acknowledge(
+      nullptr, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel}),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqAcknowledgeTest, BadIrq) {
+  EXPECT_EQ(dif_${ip.name_snake}_irq_acknowledge(
+      nullptr, 
+      (dif_${ip.name_snake}_irq_t)32),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqAcknowledgeTest, Success) {
+  // Clear the first IRQ state.
+  EXPECT_WRITE32(${ip.name_upper}_INTR_STATE_REG_OFFSET,
+                 {{${ip.name_upper}_INTR_STATE_${irqs[0].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_acknowledge(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel}),
+    kDif${ip.name_camel}Ok);
+
+  // Clear the last IRQ state.
+  EXPECT_WRITE32(${ip.name_upper}_INTR_STATE_REG_OFFSET,
+                 {{${ip.name_upper}_INTR_STATE_${irqs[-1].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_acknowledge(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[-1].name_camel}),
+    kDif${ip.name_camel}Ok);
+}
+
+class IrqGetEnabledTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqGetEnabledTest, NullArgs) {
+  dif_${ip.name_snake}_toggle_t irq_state;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_enabled(
+      nullptr, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      &irq_state),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_enabled(
+      &${ip.name_snake}_, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_enabled(
+      nullptr, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqGetEnabledTest, BadIrq) {
+  dif_${ip.name_snake}_toggle_t irq_state;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_enabled(
+      &${ip.name_snake}_, 
+      (dif_${ip.name_snake}_irq_t)32,
+      &irq_state),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqGetEnabledTest, Success) {
+  dif_${ip.name_snake}_toggle_t irq_state;
+
+  // First IRQ is enabled.
+  irq_state = kDif${ip.name_camel}ToggleDisabled;
+  EXPECT_READ32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET,
+                {{${ip.name_upper}_INTR_ENABLE_${irqs[0].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_enabled(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      &irq_state),
+    kDif${ip.name_camel}Ok);
+  EXPECT_EQ(irq_state, kDif${ip.name_camel}ToggleEnabled);
+
+  // Last IRQ is disabled.
+  irq_state = kDif${ip.name_camel}ToggleEnabled;
+  EXPECT_READ32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET,
+                {{${ip.name_upper}_INTR_ENABLE_${irqs[-1].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_get_enabled(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      &irq_state),
+    kDif${ip.name_camel}Ok);
+  EXPECT_EQ(irq_state, kDif${ip.name_camel}ToggleDisabled);
+}
+
+class IrqSetEnabledTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqSetEnabledTest, NullArgs) {
+  dif_${ip.name_snake}_toggle_t irq_state = kDif${ip.name_camel}ToggleEnabled;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_set_enabled(
+      nullptr, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      irq_state),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqSetEnabledTest, BadIrq) {
+  dif_${ip.name_snake}_toggle_t irq_state = kDif${ip.name_camel}ToggleEnabled;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_set_enabled(
+      &${ip.name_snake}_, 
+      (dif_${ip.name_snake}_irq_t)32,
+      irq_state),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqSetEnabledTest, Success) {
+  dif_${ip.name_snake}_toggle_t irq_state;
+
+  // Enable first IRQ.
+  irq_state = kDif${ip.name_camel}ToggleEnabled;
+  EXPECT_MASK32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET,
+                {{${ip.name_upper}_INTR_ENABLE_${irqs[0].name_upper}_BIT,
+                  0x1,
+                  true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_set_enabled(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel},
+      irq_state),
+    kDif${ip.name_camel}Ok);
+
+  // Disable last IRQ.
+  irq_state = kDif${ip.name_camel}ToggleDisabled;
+  EXPECT_MASK32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET,
+                {{${ip.name_upper}_INTR_ENABLE_${irqs[-1].name_upper}_BIT, 
+                  0x1, 
+                  false}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_set_enabled(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[-1].name_camel},
+      irq_state),
+    kDif${ip.name_camel}Ok);
+}
+
+class IrqForceTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqForceTest, NullArgs) {
+  EXPECT_EQ(dif_${ip.name_snake}_irq_force(
+      nullptr, 
+      kDif${ip.name_camel}Irq${irqs[0].name_camel}),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqForceTest, BadIrq) {
+  EXPECT_EQ(dif_${ip.name_snake}_irq_force(
+      nullptr, 
+      (dif_${ip.name_snake}_irq_t)32),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqForceTest, Success) {
+  // Force first IRQ.
+  EXPECT_WRITE32(${ip.name_upper}_INTR_TEST_REG_OFFSET,
+                 {{${ip.name_upper}_INTR_TEST_${irqs[0].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_force(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[0].name_camel}),
+    kDif${ip.name_camel}Ok);
+
+  // Force last IRQ.
+  EXPECT_WRITE32(${ip.name_upper}_INTR_TEST_REG_OFFSET,
+                 {{${ip.name_upper}_INTR_TEST_${irqs[-1].name_upper}_BIT, true}});
+  EXPECT_EQ(dif_${ip.name_snake}_irq_force(
+      &${ip.name_snake}_,
+      kDif${ip.name_camel}Irq${irqs[-1].name_camel}),
+    kDif${ip.name_camel}Ok);
+}
+
+class IrqDisableAllTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqDisableAllTest, NullArgs) {
+  dif_${ip.name_snake}_irq_enable_snapshot_t irq_snapshot;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_disable_all(
+      nullptr, 
+      &irq_snapshot),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_disable_all(
+      nullptr, 
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqDisableAllTest, SuccessNoSnapshot) {
+  EXPECT_WRITE32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET, 0);
+  EXPECT_EQ(dif_${ip.name_snake}_irq_disable_all(
+      &${ip.name_snake}_, 
+      nullptr),
+    kDif${ip.name_camel}Ok);
+}
+
+TEST_F(IrqDisableAllTest, SuccessSnapshotAllDisabled) {
+  dif_${ip.name_snake}_irq_enable_snapshot_t irq_snapshot;
+
+  EXPECT_READ32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET, 0);
+  EXPECT_WRITE32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET, 0);
+  EXPECT_EQ(dif_${ip.name_snake}_irq_disable_all(
+      &${ip.name_snake}_, 
+      &irq_snapshot),
+    kDif${ip.name_camel}Ok);
+  EXPECT_EQ(irq_snapshot, 0);
+}
+
+TEST_F(IrqDisableAllTest, SuccessSnapshotAllEnabled) {
+  dif_${ip.name_snake}_irq_enable_snapshot_t irq_snapshot;
+
+  EXPECT_READ32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET,
+                std::numeric_limits<uint32_t>::max());
+  EXPECT_WRITE32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET, 0);
+  EXPECT_EQ(dif_${ip.name_snake}_irq_disable_all(
+      &${ip.name_snake}_, 
+      &irq_snapshot),
+    kDif${ip.name_camel}Ok);
+  EXPECT_EQ(irq_snapshot, std::numeric_limits<uint32_t>::max());
+}
+
+class IrqRestoreAllTest : public ${ip.name_camel}Test {};
+
+TEST_F(IrqRestoreAllTest, NullArgs) {
+  dif_${ip.name_snake}_irq_enable_snapshot_t irq_snapshot;
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_restore_all(
+      nullptr, 
+      &irq_snapshot),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_restore_all(
+      &${ip.name_snake}_, 
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+
+  EXPECT_EQ(dif_${ip.name_snake}_irq_restore_all(
+      nullptr, 
+      nullptr),
+    kDif${ip.name_camel}BadArg);
+}
+
+TEST_F(IrqRestoreAllTest, SuccessAllEnabled) {
+  dif_${ip.name_snake}_irq_enable_snapshot_t irq_snapshot = 
+    std::numeric_limits<uint32_t>::max();
+
+  EXPECT_WRITE32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET, 
+    std::numeric_limits<uint32_t>::max());
+  EXPECT_EQ(dif_${ip.name_snake}_irq_restore_all(
+      &${ip.name_snake}_, 
+      &irq_snapshot),
+    kDif${ip.name_camel}Ok);
+}
+
+TEST_F(IrqRestoreAllTest, SuccessAllDisabled) {
+  dif_${ip.name_snake}_irq_enable_snapshot_t irq_snapshot = 0;
+
+  EXPECT_WRITE32(${ip.name_upper}_INTR_ENABLE_REG_OFFSET, 0);
+  EXPECT_EQ(dif_${ip.name_snake}_irq_restore_all(
+      &${ip.name_snake}_, 
+      &irq_snapshot),
+    kDif${ip.name_camel}Ok);
+}
+
+}  // namespace
+}  // namespace dif_${ip.name_snake}_unittest
diff --git a/util/make_new_dif/dif_template.h.tpl b/util/make_new_dif/dif_template.h.tpl
new file mode 100644
index 0000000..871dee9
--- /dev/null
+++ b/util/make_new_dif/dif_template.h.tpl
@@ -0,0 +1,197 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+<%doc>
+    This file is the "DIF library header template", which provides a base for
+    manually-implmenting portions of a DIF for a new peripheral, defining all of
+    the declarations that would be expected of a DIF library as described in the
+    README.md. This header includes the auto-generated DIF library header, whose
+    template is defined in util/make_new_dif/dif_autogen.inc.tpl.
+
+    This file should be instantiated with the `util/make_new_dif.py` script.
+
+    The script also includes additional options for controlling how the template
+    is instantiated. After the script runs:
+       - delete this comment, 
+       - the #error below, and 
+       - any unnecessary definitions.
+    Finally, remember to run clang-format!
+
+    Note, this template requires the following Python objects to be passed:
+
+    1. ip: See util/make_new_dif.py for the definition of the `ip` obj.
+</%doc>
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_DIF_DIF_${ip.name_upper}_H_
+#define OPENTITAN_SW_DEVICE_LIB_DIF_DIF_${ip.name_upper}_H_
+
+
+#error "This file is a template, and not real code."
+
+/**
+ * @file
+ * @brief <a href="/hw/ip/${ip.name_snake}/doc/">${ip.name_long_upper}</a> Device Interface Functions
+ */
+
+#include <stdint.h>
+
+#include "sw/device/lib/dif/dif_warn_unused_result.h"
+
+#include "sw/device/lib/dif/autogen/dif_${ip.name_snake}_autogen.inc"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * Hardware instantiation parameters for ${ip.name_long_lower}.
+ *
+ * This struct describes information about the underlying HARDWARE that is
+ * not determined until the hardware IP is used as part of a top-level
+ * design. This EXCLUDES the base address of the IP as this is defined the IP's
+ * handle struct which is autogenerated using the 
+ * util/make_new_dif/dif_autogen.h.tpl template. This is typically only used
+ * with templated (generic) IPs, such as the Alert Handler.
+ */
+typedef struct dif_${ip.name_snake}_params {
+  // Fields, if necessary.
+  // DO NOT include the IP's base address as a field, see above.
+} dif_${ip.name_snake}_params_t;
+
+/**
+ * Runtime configuration for ${ip.name_long_lower}.
+ *
+ * This struct describes (SOFTWARE) runtime information for one-time
+ * configuration of the hardware.
+ */
+typedef struct dif_${ip.name_snake}_config {
+  // Fields, if necessary.
+} dif_${ip.name_snake}_config_t;
+
+/**
+ * Additional results of a ${ip.name_long_lower} operation.
+ * 
+ * Note: base result values are defined in the fully autogenerated header, see
+ * the util/make_new_dif/dif_autogen.h.tpl template, there fore these values
+ * must start at 3.
+ */
+typedef enum dif_${ip.name_snake}_result_ext {
+  // Remove this variant if you don't need it.
+  kDif${ip.name_camel}Locked = 3,
+  // Additional variants if required..
+} dif_${ip.name_snake}_result_ext_t;
+
+/**
+ * Parameters for a ${ip.name_long_lower} transaction.
+ */
+typedef struct dif_${ip.name_snake}_transaction {
+  // Your fields here.
+} dif_${ip.name_snake}_transaction_t;
+
+/**
+ * An output location for a ${ip.name_long_lower} transaction.
+ */
+typedef struct dif_${ip.name_snake}_output {
+  // Your fields here.
+} dif_${ip.name_snake}_output_t;
+
+/**
+ * Calculates information needed to safely call a DIF. Functions like this
+ * should be used instead of global variables or #defines.
+ *
+ * This function does not actuate the hardware.
+ *
+ * @param params Hardware instantiation parameters.
+ * @return The information required.
+ */
+DIF_WARN_UNUSED_RESULT
+uint32_t dif_${ip.name_snake}_get_size(dif_${ip.name_snake}_params_t params);
+
+/**
+ * Creates a new handle for ${ip.name_long_lower}.
+ *
+ * This function does not actuate the hardware.
+ *
+ * @param base_addr The MMIO base address of the IP.
+ * @param params Hardware instantiation parameters. (optional, may remove)
+ * @param[out] ${ip.name_snake} Out param for the initialized handle.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_init(
+  mmio_region_t base_addr,
+  dif_${ip.name_snake}_params_t params,
+  dif_${ip.name_snake}_t *${ip.name_snake});
+
+/**
+ * Configures ${ip.name_long_lower} with runtime information.
+ *
+ * This function should only need to be called once for the lifetime of
+ * `handle`.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param config Runtime configuration parameters.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_configure(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_config_t config);
+
+/**
+ * Begins a ${ip.name_long_lower} transaction.
+ *
+ * Each call to this function should be sequenced with a call to
+ * `dif_${ip.name_snake}_end()`.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param transaction Transaction configuration parameters.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_start(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_transaction_t transaction);
+
+/** Ends a ${ip.name_long_lower} transaction, writing the results to the given output..
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param output Transaction output parameters.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_end(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  dif_${ip.name_snake}_output_t output);
+
+/**
+ * Locks out ${ip.name_long_lower} functionality.
+ *
+ * This function is reentrant: calling it while functionality is locked will
+ * have no effect and return `kDif${ip.name_camel}Ok`.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_lock(
+  const dif_${ip.name_snake}_t *${ip.name_snake});
+
+/**
+ * Checks whether this ${ip.name_long_lower} is locked.
+ *
+ * @param ${ip.name_snake} A ${ip.name_long_lower} handle.
+ * @param[out] is_locked Out-param for the locked state.
+ * @return The result of the operation.
+ */
+DIF_WARN_UNUSED_RESULT
+dif_${ip.name_snake}_result_t dif_${ip.name_snake}_is_locked(
+  const dif_${ip.name_snake}_t *${ip.name_snake},
+  bool *is_locked);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif  // OPENTITAN_SW_DEVICE_LIB_DIF_DIF_${ip.name_upper}_H_