[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.
+
+
+
+## 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",