[nmi_gen] Add the nmi_gen peripheral for alert_handler testing purposes

The nmi_gen peripheral is a simple wrapper module that receives the
escalation signals from the alert handler and converts them to
interrupts for the core. These are currently not true non-maskable
interrupts. The module is only used for testing the alert handler
mechanism at the moment, and will be adapted and/or replaced in the future.

Signed-off-by: Michael Schaffner <msf@google.com>
diff --git a/hw/Makefile b/hw/Makefile
index 0225db9..9997031 100644
--- a/hw/Makefile
+++ b/hw/Makefile
@@ -12,7 +12,8 @@
        usbuart       \
        alert_handler \
        pinmux        \
-       padctrl
+       padctrl       \
+       nmi_gen
 
 TOPS ?= top_earlgrey
 
diff --git a/hw/_index.md b/hw/_index.md
index 030850e..77b2922 100644
--- a/hw/_index.md
+++ b/hw/_index.md
@@ -22,6 +22,7 @@
 | `gpio`          | [design spec]({{< relref "hw/ip/gpio/doc" >}})          | [DV plan]({{< relref "hw/ip/gpio/doc/dv_plan" >}}) |
 | `hmac`          | [design spec]({{< relref "hw/ip/hmac/doc" >}})          | [DV plan]({{< relref "hw/ip/hmac/doc/dv_plan" >}}) |
 | `i2c`           | [design spec]({{< relref "hw/ip/i2c/doc" >}})           | [DV plan]({{< relref "hw/ip/i2c/doc/dv_plan" >}})  |
+| `nmi_gen`       | [design spec]({{< relref "hw/ip/nmi_gen/doc" >}})       | |
 | `padctrl`       | [design spec]({{< relref "hw/ip/padctrl/doc" >}})       | |
 | `pinmux`        | [design spec]({{< relref "hw/ip/pinmux/doc" >}})        | |
 | `rv_core_ibex`  | [design spec]({{< relref "hw/ip/rv_core_ibex/doc" >}})  | |
diff --git a/hw/ip/alert_handler/doc/_index.md b/hw/ip/alert_handler/doc/_index.md
index c7e5d1b..56cd326 100644
--- a/hw/ip/alert_handler/doc/_index.md
+++ b/hw/ip/alert_handler/doc/_index.md
@@ -930,7 +930,7 @@
 {{< registers "hw/ip/alert_handler/data/alert_handler.hjson" >}}
 
 
-# Additiona Notes
+# Additional Notes
 
 ## Timing Constraints
 
diff --git a/hw/ip/nmi_gen/data/nmi_gen.hjson b/hw/ip/nmi_gen/data/nmi_gen.hjson
new file mode 100644
index 0000000..088bd5c
--- /dev/null
+++ b/hw/ip/nmi_gen/data/nmi_gen.hjson
@@ -0,0 +1,36 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+# NMI_GEN register template
+{
+  name: "NMI_GEN",
+  clock_primary: "clk_i",
+  bus_device: "tlul",
+  regwidth: "32",
+  interrupt_list: [
+    { name: "esc0",
+      desc: '''
+            Escalation interrupt 0
+            ''',
+    },
+    { name: "esc1",
+      desc: '''
+            Escalation interrupt 1
+            ''',
+    },
+    { name: "esc2",
+      desc: '''
+            Escalation interrupt 2
+            ''',
+    },
+    { name: "esc3",
+      desc: '''
+            Escalation interrupt 3
+            ''',
+    },
+  ],
+  registers: [
+  ],
+}
+
diff --git a/hw/ip/nmi_gen/data/nmi_gen.prj.hjson b/hw/ip/nmi_gen/data/nmi_gen.prj.hjson
new file mode 100644
index 0000000..53ddfc3
--- /dev/null
+++ b/hw/ip/nmi_gen/data/nmi_gen.prj.hjson
@@ -0,0 +1,12 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+{
+    name:               "nmi_gen",
+    version:            "0.5",
+    life_stage:         "L1",
+    design_stage:       "D0",
+    verification_stage: "V0",
+    notes:              "will be verified at top level; formal at block level",
+}
diff --git a/hw/ip/nmi_gen/doc/index.md b/hw/ip/nmi_gen/doc/index.md
new file mode 100644
index 0000000..50378fd
--- /dev/null
+++ b/hw/ip/nmi_gen/doc/index.md
@@ -0,0 +1,47 @@
+---
+title: "NMI Generator Technical Specification"
+---
+
+# Overview
+
+This document specifies the functionality of the non-maskable interrupt generator (`nmi_gen`) peripheral.
+This module conforms to the [OpenTitan guideline for peripheral device functionality.]({{< relref "doc/rm/comportability_specification" >}}).
+See that document for integration overview within the broader OpenTitan top level system.
+The module provides a mechanism to test the alert handler escalation signals (see [alert handler spec]({{< ref "hw/ip/alert_handler/doc" >}})), and will be modified or even replaced in the near future.
+
+
+## Features
+
+- Contains four escalation receivers to receive escalation signals from the alert handler
+- Produces four regular interrupts that are derived from the alert handler escalation signals
+
+## Description
+
+The NMI generator module is a simple wrapper module that instantiates four escalation receivers (which are described in more detail in the alert handler spec).
+The output of these receivers are converted into regular interrupt requests that can be connected to the PLIC/processor core in order to test the escalation mechanism.
+
+![NMI Generator Block Diagram](nmi_gen_blockdiag.svg)
+
+## Parameters
+
+The following table lists the main parameters used throughout the `nmi_gen` design.
+
+Localparam     | Default (Max)         | Description
+---------------|-----------------------|---------------
+N_ESC_SEV      | 4 (-)                 | Number of escalation receivers (should be left as is).
+
+## Signals
+
+{{< hwcfg "hw/ip/nmi_gen/data/nmi_gen.hjson" >}}
+
+The table below lists other `nmi_gen` signals.
+
+Signal                               | Direction        | Type                    | Description
+-------------------------------------|------------------|-------------------------|---------------
+`esc_tx_i[N_ESC_SEV-1:0]`            | `input`          | packed `esc_tx_t` array | Differentially encoded escalation signals coming from the alert handler.
+`esc_rx_o[N_ESC_SEV-1:0]`            | `output`         | packed `esc_rx_t` array | Differentially encoded response signals to alert handler.
+
+
+## Register Table
+
+{{< registers "hw/ip/nmi_gen/data/nmi_gen.hjson" >}}
diff --git a/hw/ip/nmi_gen/dv/env/nmi_gen_reg_block.sv b/hw/ip/nmi_gen/dv/env/nmi_gen_reg_block.sv
new file mode 100644
index 0000000..9997ef0
--- /dev/null
+++ b/hw/ip/nmi_gen/dv/env/nmi_gen_reg_block.sv
@@ -0,0 +1,260 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// UVM Registers auto-generated by `reggen` containing data structure
+// Do Not Edit directly
+
+// Block: nmi_gen
+`ifndef NMI_GEN_REG_BLOCK__SV
+`define NMI_GEN_REG_BLOCK__SV
+
+// Forward declare all register/memory/block classes
+typedef class nmi_gen_reg_intr_state;
+typedef class nmi_gen_reg_intr_enable;
+typedef class nmi_gen_reg_intr_test;
+typedef class nmi_gen_reg_block;
+
+// Class: nmi_gen_reg_intr_state
+class nmi_gen_reg_intr_state extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field esc0;
+  rand dv_base_reg_field esc1;
+  rand dv_base_reg_field esc2;
+  rand dv_base_reg_field esc3;
+
+  `uvm_object_utils(nmi_gen_reg_intr_state)
+
+  function new(string       name = "nmi_gen_reg_intr_state",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    esc0 = dv_base_reg_field::type_id::create("esc0");
+    esc0.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("W1C"),
+      .volatile(1),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc1 = dv_base_reg_field::type_id::create("esc1");
+    esc1.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("W1C"),
+      .volatile(1),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc2 = dv_base_reg_field::type_id::create("esc2");
+    esc2.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("W1C"),
+      .volatile(1),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc3 = dv_base_reg_field::type_id::create("esc3");
+    esc3.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("W1C"),
+      .volatile(1),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : nmi_gen_reg_intr_state
+
+// Class: nmi_gen_reg_intr_enable
+class nmi_gen_reg_intr_enable extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field esc0;
+  rand dv_base_reg_field esc1;
+  rand dv_base_reg_field esc2;
+  rand dv_base_reg_field esc3;
+
+  `uvm_object_utils(nmi_gen_reg_intr_enable)
+
+  function new(string       name = "nmi_gen_reg_intr_enable",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    esc0 = dv_base_reg_field::type_id::create("esc0");
+    esc0.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("RW"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc1 = dv_base_reg_field::type_id::create("esc1");
+    esc1.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("RW"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc2 = dv_base_reg_field::type_id::create("esc2");
+    esc2.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("RW"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc3 = dv_base_reg_field::type_id::create("esc3");
+    esc3.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("RW"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : nmi_gen_reg_intr_enable
+
+// Class: nmi_gen_reg_intr_test
+class nmi_gen_reg_intr_test extends dv_base_reg;
+  // fields
+  rand dv_base_reg_field esc0;
+  rand dv_base_reg_field esc1;
+  rand dv_base_reg_field esc2;
+  rand dv_base_reg_field esc3;
+
+  `uvm_object_utils(nmi_gen_reg_intr_test)
+
+  function new(string       name = "nmi_gen_reg_intr_test",
+               int unsigned n_bits = 32,
+               int          has_coverage = UVM_NO_COVERAGE);
+    super.new(name, n_bits, has_coverage);
+  endfunction : new
+
+  virtual function void build();
+    // create fields
+    esc0 = dv_base_reg_field::type_id::create("esc0");
+    esc0.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(0),
+      .access("WO"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc1 = dv_base_reg_field::type_id::create("esc1");
+    esc1.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(1),
+      .access("WO"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc2 = dv_base_reg_field::type_id::create("esc2");
+    esc2.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(2),
+      .access("WO"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+    esc3 = dv_base_reg_field::type_id::create("esc3");
+    esc3.configure(
+      .parent(this),
+      .size(1),
+      .lsb_pos(3),
+      .access("WO"),
+      .volatile(0),
+      .reset(32'h0),
+      .has_reset(1),
+      .is_rand(1),
+      .individually_accessible(1));
+  endfunction : build
+
+endclass : nmi_gen_reg_intr_test
+
+// Class: nmi_gen_reg_block
+class nmi_gen_reg_block extends dv_base_reg_block;
+  // registers
+  rand nmi_gen_reg_intr_state intr_state;
+  rand nmi_gen_reg_intr_enable intr_enable;
+  rand nmi_gen_reg_intr_test intr_test;
+
+  `uvm_object_utils(nmi_gen_reg_block)
+
+  function new(string name = "nmi_gen_reg_block",
+               int    has_coverage = UVM_NO_COVERAGE);
+    super.new(name, has_coverage);
+  endfunction : new
+
+  virtual function void build(uvm_reg_addr_t base_addr);
+    // create default map
+    this.default_map = create_map(.name("default_map"),
+                                  .base_addr(base_addr),
+                                  .n_bytes(4),
+                                  .endian(UVM_LITTLE_ENDIAN));
+
+    // create registers
+    intr_state = nmi_gen_reg_intr_state::type_id::create("intr_state");
+    intr_state.configure(.blk_parent(this));
+    intr_state.build();
+    default_map.add_reg(.rg(intr_state),
+                        .offset(32'h0),
+                        .rights("RW"));
+    intr_enable = nmi_gen_reg_intr_enable::type_id::create("intr_enable");
+    intr_enable.configure(.blk_parent(this));
+    intr_enable.build();
+    default_map.add_reg(.rg(intr_enable),
+                        .offset(32'h4),
+                        .rights("RW"));
+    intr_test = nmi_gen_reg_intr_test::type_id::create("intr_test");
+    intr_test.configure(.blk_parent(this));
+    intr_test.build();
+    default_map.add_reg(.rg(intr_test),
+                        .offset(32'h8),
+                        .rights("WO"));
+  endfunction : build
+
+endclass : nmi_gen_reg_block
+
+`endif
diff --git a/hw/ip/nmi_gen/nmi_gen.core b/hw/ip/nmi_gen/nmi_gen.core
new file mode 100644
index 0000000..db5b365
--- /dev/null
+++ b/hw/ip/nmi_gen/nmi_gen.core
@@ -0,0 +1,25 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:ip:nmi_gen:0.1"
+description: "NMI Gen"
+
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:ip:tlul
+      - lowrisc:prim:all
+    files:
+      - rtl/nmi_gen_reg_pkg.sv
+      - rtl/nmi_gen_reg_top.sv
+      - rtl/nmi_gen.sv
+    file_type: systemVerilogSource
+
+targets:
+  default: &default_target
+    filesets:
+      - files_rtl
+    toplevel: nmi_gen
+
+
diff --git a/hw/ip/nmi_gen/rtl/nmi_gen.sv b/hw/ip/nmi_gen/rtl/nmi_gen.sv
new file mode 100644
index 0000000..d5c1be2
--- /dev/null
+++ b/hw/ip/nmi_gen/rtl/nmi_gen.sv
@@ -0,0 +1,115 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// NMI generator. This is a simple helper unit that wraps the escalation signal
+// receivers and converts them into interrupts such that they can be tested in system.
+// See also alert handler documentation for more context.
+
+module nmi_gen import prim_pkg::*; #(
+  // leave constant
+  localparam int unsigned N_ESC_SEV = 4
+) (
+  input                           clk_i,
+  input                           rst_ni,
+  // Bus Interface (device)
+  input  tlul_pkg::tl_h2d_t       tl_i,
+  output tlul_pkg::tl_d2h_t       tl_o,
+  // Interrupt Requests
+  output logic                    intr_esc0_o,
+  output logic                    intr_esc1_o,
+  output logic                    intr_esc2_o,
+  output logic                    intr_esc3_o,
+  // Escalation outputs
+  input  esc_tx_t [N_ESC_SEV-1:0] esc_tx_i,
+  output esc_rx_t [N_ESC_SEV-1:0] esc_rx_o
+);
+
+  //////////////////////
+  // Regfile instance //
+  //////////////////////
+
+  logic [N_ESC_SEV-1:0] esc_en;
+  nmi_gen_reg_pkg::nmi_gen_reg2hw_t reg2hw;
+  nmi_gen_reg_pkg::nmi_gen_hw2reg_t hw2reg;
+
+  nmi_gen_reg_top i_reg (
+    .clk_i,
+    .rst_ni,
+    .tl_i,
+    .tl_o,
+    .reg2hw,
+    .hw2reg,
+    .devmode_i(1'b1)
+  );
+
+  ////////////////
+  // Interrupts //
+  ////////////////
+
+  prim_intr_hw #(
+    .Width(1)
+  ) i_intr_esc0 (
+    .event_intr_i           ( esc_en[0]                  ),
+    .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.esc0.q  ),
+    .reg2hw_intr_test_q_i   ( reg2hw.intr_test.esc0.q    ),
+    .reg2hw_intr_test_qe_i  ( reg2hw.intr_test.esc0.qe   ),
+    .reg2hw_intr_state_q_i  ( reg2hw.intr_state.esc0.q   ),
+    .hw2reg_intr_state_de_o ( hw2reg.intr_state.esc0.de  ),
+    .hw2reg_intr_state_d_o  ( hw2reg.intr_state.esc0.d   ),
+    .intr_o                 ( intr_esc0_o                )
+  );
+
+  prim_intr_hw #(
+    .Width(1)
+  ) i_intr_esc1 (
+    .event_intr_i           ( esc_en[1]                  ),
+    .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.esc1.q  ),
+    .reg2hw_intr_test_q_i   ( reg2hw.intr_test.esc1.q    ),
+    .reg2hw_intr_test_qe_i  ( reg2hw.intr_test.esc1.qe   ),
+    .reg2hw_intr_state_q_i  ( reg2hw.intr_state.esc1.q   ),
+    .hw2reg_intr_state_de_o ( hw2reg.intr_state.esc1.de  ),
+    .hw2reg_intr_state_d_o  ( hw2reg.intr_state.esc1.d   ),
+    .intr_o                 ( intr_esc1_o                )
+  );
+
+  prim_intr_hw #(
+    .Width(1)
+  ) i_intr_esc2 (
+    .event_intr_i           ( esc_en[2]                  ),
+    .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.esc2.q  ),
+    .reg2hw_intr_test_q_i   ( reg2hw.intr_test.esc2.q    ),
+    .reg2hw_intr_test_qe_i  ( reg2hw.intr_test.esc2.qe   ),
+    .reg2hw_intr_state_q_i  ( reg2hw.intr_state.esc2.q   ),
+    .hw2reg_intr_state_de_o ( hw2reg.intr_state.esc2.de  ),
+    .hw2reg_intr_state_d_o  ( hw2reg.intr_state.esc2.d   ),
+    .intr_o                 ( intr_esc2_o                )
+  );
+
+  prim_intr_hw #(
+    .Width(1)
+  ) i_intr_esc3 (
+    .event_intr_i           ( esc_en[3]                  ),
+    .reg2hw_intr_enable_q_i ( reg2hw.intr_enable.esc3.q  ),
+    .reg2hw_intr_test_q_i   ( reg2hw.intr_test.esc3.q    ),
+    .reg2hw_intr_test_qe_i  ( reg2hw.intr_test.esc3.qe   ),
+    .reg2hw_intr_state_q_i  ( reg2hw.intr_state.esc3.q   ),
+    .hw2reg_intr_state_de_o ( hw2reg.intr_state.esc3.de  ),
+    .hw2reg_intr_state_d_o  ( hw2reg.intr_state.esc3.d   ),
+    .intr_o                 ( intr_esc3_o                )
+  );
+
+  /////////////////////////////////////////
+  // Connect escalation signal receivers //
+  /////////////////////////////////////////
+  for (genvar k = 0; k < N_ESC_SEV; k++) begin : gen_esc_sev
+    prim_esc_receiver i_prim_esc_receiver (
+      .clk_i,
+      .rst_ni,
+      .esc_en_o ( esc_en[k]   ),
+      .esc_rx_o ( esc_rx_o[k] ),
+      .esc_tx_i ( esc_tx_i[k] )
+    );
+  end : gen_esc_sev
+
+endmodule : nmi_gen
diff --git a/hw/ip/nmi_gen/rtl/nmi_gen_reg_pkg.sv b/hw/ip/nmi_gen/rtl/nmi_gen_reg_pkg.sv
new file mode 100644
index 0000000..e0bc707
--- /dev/null
+++ b/hw/ip/nmi_gen/rtl/nmi_gen_reg_pkg.sv
@@ -0,0 +1,107 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Register Package auto-generated by `reggen` containing data structure
+
+package nmi_gen_reg_pkg;
+
+///////////////////////////////////////
+// Register to internal design logic //
+///////////////////////////////////////
+
+typedef struct packed {
+  struct packed {
+    struct packed {
+      logic q; // [15]
+    } esc0;
+    struct packed {
+      logic q; // [14]
+    } esc1;
+    struct packed {
+      logic q; // [13]
+    } esc2;
+    struct packed {
+      logic q; // [12]
+    } esc3;
+  } intr_state;
+  struct packed {
+    struct packed {
+      logic q; // [11]
+    } esc0;
+    struct packed {
+      logic q; // [10]
+    } esc1;
+    struct packed {
+      logic q; // [9]
+    } esc2;
+    struct packed {
+      logic q; // [8]
+    } esc3;
+  } intr_enable;
+  struct packed {
+    struct packed {
+      logic q; // [7]
+      logic qe; // [6]
+    } esc0;
+    struct packed {
+      logic q; // [5]
+      logic qe; // [4]
+    } esc1;
+    struct packed {
+      logic q; // [3]
+      logic qe; // [2]
+    } esc2;
+    struct packed {
+      logic q; // [1]
+      logic qe; // [0]
+    } esc3;
+  } intr_test;
+} nmi_gen_reg2hw_t;
+
+///////////////////////////////////////
+// Internal design logic to register //
+///////////////////////////////////////
+
+typedef struct packed {
+  struct packed {
+    struct packed {
+      logic d; // [7]
+      logic de; // [6]
+    } esc0;
+    struct packed {
+      logic d; // [5]
+      logic de; // [4]
+    } esc1;
+    struct packed {
+      logic d; // [3]
+      logic de; // [2]
+    } esc2;
+    struct packed {
+      logic d; // [1]
+      logic de; // [0]
+    } esc3;
+  } intr_state;
+} nmi_gen_hw2reg_t;
+
+  // Register Address
+  parameter NMI_GEN_INTR_STATE_OFFSET = 4'h 0;
+  parameter NMI_GEN_INTR_ENABLE_OFFSET = 4'h 4;
+  parameter NMI_GEN_INTR_TEST_OFFSET = 4'h 8;
+
+
+  // Register Index
+  typedef enum int {
+    NMI_GEN_INTR_STATE,
+    NMI_GEN_INTR_ENABLE,
+    NMI_GEN_INTR_TEST
+  } nmi_gen_id_e;
+
+  // Register width information to check illegal writes
+  localparam logic [3:0] NMI_GEN_PERMIT [3] = '{
+    4'b 0001, // index[0] NMI_GEN_INTR_STATE
+    4'b 0001, // index[1] NMI_GEN_INTR_ENABLE
+    4'b 0001  // index[2] NMI_GEN_INTR_TEST
+  };
+endpackage
+
diff --git a/hw/ip/nmi_gen/rtl/nmi_gen_reg_top.sv b/hw/ip/nmi_gen/rtl/nmi_gen_reg_top.sv
new file mode 100644
index 0000000..e827d3b
--- /dev/null
+++ b/hw/ip/nmi_gen/rtl/nmi_gen_reg_top.sv
@@ -0,0 +1,479 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Register Top module auto-generated by `reggen`
+
+module nmi_gen_reg_top (
+  input clk_i,
+  input rst_ni,
+
+  // Below Regster interface can be changed
+  input  tlul_pkg::tl_h2d_t tl_i,
+  output tlul_pkg::tl_d2h_t tl_o,
+  // To HW
+  output nmi_gen_reg_pkg::nmi_gen_reg2hw_t reg2hw, // Write
+  input  nmi_gen_reg_pkg::nmi_gen_hw2reg_t hw2reg, // Read
+
+  // Config
+  input devmode_i // If 1, explicit error return for unmapped register access
+);
+
+  import nmi_gen_reg_pkg::* ;
+
+  localparam AW = 4;
+  localparam DW = 32;
+  localparam DBW = DW/8;                    // Byte Width
+
+  // register signals
+  logic           reg_we;
+  logic           reg_re;
+  logic [AW-1:0]  reg_addr;
+  logic [DW-1:0]  reg_wdata;
+  logic [DBW-1:0] reg_be;
+  logic [DW-1:0]  reg_rdata;
+  logic           reg_error;
+
+  logic          addrmiss, wr_err;
+
+  logic [DW-1:0] reg_rdata_next;
+
+  tlul_pkg::tl_h2d_t tl_reg_h2d;
+  tlul_pkg::tl_d2h_t tl_reg_d2h;
+
+  assign tl_reg_h2d = tl_i;
+  assign tl_o       = tl_reg_d2h;
+
+  tlul_adapter_reg #(
+    .RegAw(AW),
+    .RegDw(DW)
+  ) u_reg_if (
+    .clk_i,
+    .rst_ni,
+
+    .tl_i (tl_reg_h2d),
+    .tl_o (tl_reg_d2h),
+
+    .we_o    (reg_we),
+    .re_o    (reg_re),
+    .addr_o  (reg_addr),
+    .wdata_o (reg_wdata),
+    .be_o    (reg_be),
+    .rdata_i (reg_rdata),
+    .error_i (reg_error)
+  );
+
+  assign reg_rdata = reg_rdata_next ;
+  assign reg_error = (devmode_i & addrmiss) | wr_err ;
+
+  // Define SW related signals
+  // Format: <reg>_<field>_{wd|we|qs}
+  //        or <reg>_{wd|we|qs} if field == 1 or 0
+  logic intr_state_esc0_qs;
+  logic intr_state_esc0_wd;
+  logic intr_state_esc0_we;
+  logic intr_state_esc1_qs;
+  logic intr_state_esc1_wd;
+  logic intr_state_esc1_we;
+  logic intr_state_esc2_qs;
+  logic intr_state_esc2_wd;
+  logic intr_state_esc2_we;
+  logic intr_state_esc3_qs;
+  logic intr_state_esc3_wd;
+  logic intr_state_esc3_we;
+  logic intr_enable_esc0_qs;
+  logic intr_enable_esc0_wd;
+  logic intr_enable_esc0_we;
+  logic intr_enable_esc1_qs;
+  logic intr_enable_esc1_wd;
+  logic intr_enable_esc1_we;
+  logic intr_enable_esc2_qs;
+  logic intr_enable_esc2_wd;
+  logic intr_enable_esc2_we;
+  logic intr_enable_esc3_qs;
+  logic intr_enable_esc3_wd;
+  logic intr_enable_esc3_we;
+  logic intr_test_esc0_wd;
+  logic intr_test_esc0_we;
+  logic intr_test_esc1_wd;
+  logic intr_test_esc1_we;
+  logic intr_test_esc2_wd;
+  logic intr_test_esc2_we;
+  logic intr_test_esc3_wd;
+  logic intr_test_esc3_we;
+
+  // Register instances
+  // R[intr_state]: V(False)
+
+  //   F[esc0]: 0:0
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("W1C"),
+    .RESVAL  (1'h0)
+  ) u_intr_state_esc0 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_state_esc0_we),
+    .wd     (intr_state_esc0_wd),
+
+    // from internal hardware
+    .de     (hw2reg.intr_state.esc0.de),
+    .d      (hw2reg.intr_state.esc0.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_state.esc0.q ),
+
+    // to register interface (read)
+    .qs     (intr_state_esc0_qs)
+  );
+
+
+  //   F[esc1]: 1:1
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("W1C"),
+    .RESVAL  (1'h0)
+  ) u_intr_state_esc1 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_state_esc1_we),
+    .wd     (intr_state_esc1_wd),
+
+    // from internal hardware
+    .de     (hw2reg.intr_state.esc1.de),
+    .d      (hw2reg.intr_state.esc1.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_state.esc1.q ),
+
+    // to register interface (read)
+    .qs     (intr_state_esc1_qs)
+  );
+
+
+  //   F[esc2]: 2:2
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("W1C"),
+    .RESVAL  (1'h0)
+  ) u_intr_state_esc2 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_state_esc2_we),
+    .wd     (intr_state_esc2_wd),
+
+    // from internal hardware
+    .de     (hw2reg.intr_state.esc2.de),
+    .d      (hw2reg.intr_state.esc2.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_state.esc2.q ),
+
+    // to register interface (read)
+    .qs     (intr_state_esc2_qs)
+  );
+
+
+  //   F[esc3]: 3:3
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("W1C"),
+    .RESVAL  (1'h0)
+  ) u_intr_state_esc3 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_state_esc3_we),
+    .wd     (intr_state_esc3_wd),
+
+    // from internal hardware
+    .de     (hw2reg.intr_state.esc3.de),
+    .d      (hw2reg.intr_state.esc3.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_state.esc3.q ),
+
+    // to register interface (read)
+    .qs     (intr_state_esc3_qs)
+  );
+
+
+  // R[intr_enable]: V(False)
+
+  //   F[esc0]: 0:0
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("RW"),
+    .RESVAL  (1'h0)
+  ) u_intr_enable_esc0 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_enable_esc0_we),
+    .wd     (intr_enable_esc0_wd),
+
+    // from internal hardware
+    .de     (1'b0),
+    .d      ('0  ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_enable.esc0.q ),
+
+    // to register interface (read)
+    .qs     (intr_enable_esc0_qs)
+  );
+
+
+  //   F[esc1]: 1:1
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("RW"),
+    .RESVAL  (1'h0)
+  ) u_intr_enable_esc1 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_enable_esc1_we),
+    .wd     (intr_enable_esc1_wd),
+
+    // from internal hardware
+    .de     (1'b0),
+    .d      ('0  ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_enable.esc1.q ),
+
+    // to register interface (read)
+    .qs     (intr_enable_esc1_qs)
+  );
+
+
+  //   F[esc2]: 2:2
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("RW"),
+    .RESVAL  (1'h0)
+  ) u_intr_enable_esc2 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_enable_esc2_we),
+    .wd     (intr_enable_esc2_wd),
+
+    // from internal hardware
+    .de     (1'b0),
+    .d      ('0  ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_enable.esc2.q ),
+
+    // to register interface (read)
+    .qs     (intr_enable_esc2_qs)
+  );
+
+
+  //   F[esc3]: 3:3
+  prim_subreg #(
+    .DW      (1),
+    .SWACCESS("RW"),
+    .RESVAL  (1'h0)
+  ) u_intr_enable_esc3 (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    // from register interface
+    .we     (intr_enable_esc3_we),
+    .wd     (intr_enable_esc3_wd),
+
+    // from internal hardware
+    .de     (1'b0),
+    .d      ('0  ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (reg2hw.intr_enable.esc3.q ),
+
+    // to register interface (read)
+    .qs     (intr_enable_esc3_qs)
+  );
+
+
+  // R[intr_test]: V(True)
+
+  //   F[esc0]: 0:0
+  prim_subreg_ext #(
+    .DW    (1)
+  ) u_intr_test_esc0 (
+    .re     (1'b0),
+    .we     (intr_test_esc0_we),
+    .wd     (intr_test_esc0_wd),
+    .d      ('0),
+    .qre    (),
+    .qe     (reg2hw.intr_test.esc0.qe),
+    .q      (reg2hw.intr_test.esc0.q ),
+    .qs     ()
+  );
+
+
+  //   F[esc1]: 1:1
+  prim_subreg_ext #(
+    .DW    (1)
+  ) u_intr_test_esc1 (
+    .re     (1'b0),
+    .we     (intr_test_esc1_we),
+    .wd     (intr_test_esc1_wd),
+    .d      ('0),
+    .qre    (),
+    .qe     (reg2hw.intr_test.esc1.qe),
+    .q      (reg2hw.intr_test.esc1.q ),
+    .qs     ()
+  );
+
+
+  //   F[esc2]: 2:2
+  prim_subreg_ext #(
+    .DW    (1)
+  ) u_intr_test_esc2 (
+    .re     (1'b0),
+    .we     (intr_test_esc2_we),
+    .wd     (intr_test_esc2_wd),
+    .d      ('0),
+    .qre    (),
+    .qe     (reg2hw.intr_test.esc2.qe),
+    .q      (reg2hw.intr_test.esc2.q ),
+    .qs     ()
+  );
+
+
+  //   F[esc3]: 3:3
+  prim_subreg_ext #(
+    .DW    (1)
+  ) u_intr_test_esc3 (
+    .re     (1'b0),
+    .we     (intr_test_esc3_we),
+    .wd     (intr_test_esc3_wd),
+    .d      ('0),
+    .qre    (),
+    .qe     (reg2hw.intr_test.esc3.qe),
+    .q      (reg2hw.intr_test.esc3.q ),
+    .qs     ()
+  );
+
+
+
+
+  logic [2:0] addr_hit;
+  always_comb begin
+    addr_hit = '0;
+    addr_hit[0] = (reg_addr == NMI_GEN_INTR_STATE_OFFSET);
+    addr_hit[1] = (reg_addr == NMI_GEN_INTR_ENABLE_OFFSET);
+    addr_hit[2] = (reg_addr == NMI_GEN_INTR_TEST_OFFSET);
+  end
+
+  assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ;
+
+  // Check sub-word write is permitted
+  always_comb begin
+    wr_err = 1'b0;
+    if (addr_hit[0] && reg_we && (NMI_GEN_PERMIT[0] != (NMI_GEN_PERMIT[0] & reg_be))) wr_err = 1'b1 ;
+    if (addr_hit[1] && reg_we && (NMI_GEN_PERMIT[1] != (NMI_GEN_PERMIT[1] & reg_be))) wr_err = 1'b1 ;
+    if (addr_hit[2] && reg_we && (NMI_GEN_PERMIT[2] != (NMI_GEN_PERMIT[2] & reg_be))) wr_err = 1'b1 ;
+  end
+
+  assign intr_state_esc0_we = addr_hit[0] & reg_we & ~wr_err;
+  assign intr_state_esc0_wd = reg_wdata[0];
+
+  assign intr_state_esc1_we = addr_hit[0] & reg_we & ~wr_err;
+  assign intr_state_esc1_wd = reg_wdata[1];
+
+  assign intr_state_esc2_we = addr_hit[0] & reg_we & ~wr_err;
+  assign intr_state_esc2_wd = reg_wdata[2];
+
+  assign intr_state_esc3_we = addr_hit[0] & reg_we & ~wr_err;
+  assign intr_state_esc3_wd = reg_wdata[3];
+
+  assign intr_enable_esc0_we = addr_hit[1] & reg_we & ~wr_err;
+  assign intr_enable_esc0_wd = reg_wdata[0];
+
+  assign intr_enable_esc1_we = addr_hit[1] & reg_we & ~wr_err;
+  assign intr_enable_esc1_wd = reg_wdata[1];
+
+  assign intr_enable_esc2_we = addr_hit[1] & reg_we & ~wr_err;
+  assign intr_enable_esc2_wd = reg_wdata[2];
+
+  assign intr_enable_esc3_we = addr_hit[1] & reg_we & ~wr_err;
+  assign intr_enable_esc3_wd = reg_wdata[3];
+
+  assign intr_test_esc0_we = addr_hit[2] & reg_we & ~wr_err;
+  assign intr_test_esc0_wd = reg_wdata[0];
+
+  assign intr_test_esc1_we = addr_hit[2] & reg_we & ~wr_err;
+  assign intr_test_esc1_wd = reg_wdata[1];
+
+  assign intr_test_esc2_we = addr_hit[2] & reg_we & ~wr_err;
+  assign intr_test_esc2_wd = reg_wdata[2];
+
+  assign intr_test_esc3_we = addr_hit[2] & reg_we & ~wr_err;
+  assign intr_test_esc3_wd = reg_wdata[3];
+
+  // Read data return
+  always_comb begin
+    reg_rdata_next = '0;
+    unique case (1'b1)
+      addr_hit[0]: begin
+        reg_rdata_next[0] = intr_state_esc0_qs;
+        reg_rdata_next[1] = intr_state_esc1_qs;
+        reg_rdata_next[2] = intr_state_esc2_qs;
+        reg_rdata_next[3] = intr_state_esc3_qs;
+      end
+
+      addr_hit[1]: begin
+        reg_rdata_next[0] = intr_enable_esc0_qs;
+        reg_rdata_next[1] = intr_enable_esc1_qs;
+        reg_rdata_next[2] = intr_enable_esc2_qs;
+        reg_rdata_next[3] = intr_enable_esc3_qs;
+      end
+
+      addr_hit[2]: begin
+        reg_rdata_next[0] = '0;
+        reg_rdata_next[1] = '0;
+        reg_rdata_next[2] = '0;
+        reg_rdata_next[3] = '0;
+      end
+
+      default: begin
+        reg_rdata_next = '1;
+      end
+    endcase
+  end
+
+  // Assertions for Register Interface
+  `ASSERT_PULSE(wePulse, reg_we, clk_i, !rst_ni)
+  `ASSERT_PULSE(rePulse, reg_re, clk_i, !rst_ni)
+
+  `ASSERT(reAfterRv, $rose(reg_re || reg_we) |=> tl_o.d_valid, clk_i, !rst_ni)
+
+  `ASSERT(en2addrHit, (reg_we || reg_re) |-> $onehot0(addr_hit), clk_i, !rst_ni)
+
+  // this is formulated as an assumption such that the FPV testbenches do disprove this
+  // property by mistake
+  `ASSUME(reqParity, tl_reg_h2d.a_valid |-> tl_reg_h2d.a_user.parity_en == 1'b0, clk_i, !rst_ni)
+
+endmodule
diff --git a/util/build_docs.py b/util/build_docs.py
index a0bb0cc..494c33a 100755
--- a/util/build_docs.py
+++ b/util/build_docs.py
@@ -52,6 +52,7 @@
         "hw/ip/gpio/data/gpio.hjson",
         "hw/ip/hmac/data/hmac.hjson",
         "hw/ip/i2c/data/i2c.hjson",
+        "hw/ip/nmi_gen/data/nmi_gen.hjson",
         "hw/ip/padctrl/data/padctrl.hjson",
         "hw/ip/pinmux/data/pinmux.hjson",
         "hw/ip/rv_plic/data/rv_plic.hjson",