[sparse_fsm_flop] Create flop macro to increase DV coverage

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv b/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv
index 06d27fc..b57346c 100644
--- a/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv
+++ b/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv
@@ -7,7 +7,8 @@
 // This contains a proxy class and store the object in sec_cm_pkg, which can be used in vseq to
 // control inject_fault and restore_fault
 interface prim_sparse_fsm_flop_if #(
-  parameter int Width = 2
+  parameter int Width = 2,
+  parameter string CustomForceName = ""
 ) (
   input clk_i,
   input rst_ni);
@@ -23,6 +24,11 @@
   string path = dv_utils_pkg::get_parent_hier($sformatf("%m"));
   string signal_forced = $sformatf("%s.state_o", path);
 
+  // This signal only has to be forced if the associated parameter
+  // CustomForceName in prim_sparse_fsm_flop is set to a non-empty string.
+  string parent_path = dv_utils_pkg::get_parent_hier($sformatf("%m"), 2);
+  string custom_signal_forced = $sformatf("%s.%s", parent_path, CustomForceName);
+
   class prim_sparse_fsm_flop_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy;
     `uvm_object_new
 
@@ -36,17 +42,26 @@
       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(force_value,
           $countones(force_value ^ orig_value) inside {[1: MaxFlipBits]};)
 
-      `DV_CHECK(uvm_hdl_deposit(signal_forced, force_value))
       `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
                                   signal_forced, orig_value, force_value), UVM_LOW)
-
+      `DV_CHECK(uvm_hdl_deposit(signal_forced, force_value))
+      if (CustomForceName != "") begin
+        `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
+                                    custom_signal_forced, orig_value, force_value), UVM_LOW)
+        `DV_CHECK(uvm_hdl_deposit(custom_signal_forced, force_value))
+      end
       @(posedge clk_i);
     endtask
 
     virtual task restore_fault();
-      `DV_CHECK(uvm_hdl_deposit(signal_forced, orig_value))
       `uvm_info(msg_id, $sformatf("Forcing %s to original value %0d", signal_forced, orig_value),
                 UVM_LOW)
+      `DV_CHECK(uvm_hdl_deposit(signal_forced, orig_value))
+      if (CustomForceName != "") begin
+        `uvm_info(msg_id, $sformatf("Forcing %s to original value %0d", custom_signal_forced,
+                  orig_value), UVM_LOW)
+        `DV_CHECK(uvm_hdl_deposit(custom_signal_forced, orig_value))
+      end
     endtask
   endclass
 
diff --git a/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv b/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv
index 7bc15ae..c820882 100644
--- a/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv
+++ b/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv
@@ -4,5 +4,5 @@
 
 module sec_cm_prim_sparse_fsm_flop_bind();
   bind prim_sparse_fsm_flop prim_sparse_fsm_flop_if #(
-         .Width(Width)) u_prim_sparse_fsm_flop_if (.*);
+         .Width(Width), .CustomForceName(CustomForceName)) u_prim_sparse_fsm_flop_if (.*);
 endmodule
diff --git a/hw/ip/prim/lint/prim_assert.waiver b/hw/ip/prim/lint/prim_assert.waiver
index 8ae9e4a..f811952 100644
--- a/hw/ip/prim/lint/prim_assert.waiver
+++ b/hw/ip/prim/lint/prim_assert.waiver
@@ -6,7 +6,10 @@
 
 waive -rules {UNDEF_MACRO_REF} -location {prim_assert.sv} -regexp {Macro definition for 'ASSERT_RPT' includes expansion of undefined macro '__(FILE|LINE)__'} \
       -comment "This is an UVM specific macro inside our assertion shortcuts"
-# unfortunately most tools do not support line wrapping within the declaration of macro functions, hence we have to waive
-# line length violations.
+
+# unfortunately most tools do not support line wrapping within the declaration of macro functions,
+# hence we have to waive line length violations.
 waive -rules {LINE_LENGTH} -location {prim_assert.sv} -msg {Line length of} \
       -comment "Some macros cannot be line-wrapped, as some tools do not support that."
+waive -rules {LINE_LENGTH} -location {prim_flop_macros.sv} -msg {Line length of} \
+      -comment "Some macros cannot be line-wrapped, as some tools do not support that."
diff --git a/hw/ip/prim/lint/prim_sparse_fsm_flop.vlt b/hw/ip/prim/lint/prim_sparse_fsm_flop.vlt
index ca198d7..1ae4299 100644
--- a/hw/ip/prim/lint/prim_sparse_fsm_flop.vlt
+++ b/hw/ip/prim/lint/prim_sparse_fsm_flop.vlt
@@ -6,3 +6,4 @@
 
 // This parameter is only used in DV/FPV.
 lint_off -rule UNUSED -file "*/rtl/prim_sparse_fsm_flop.sv" -match "*EnableAlertTriggerSVA*"
+lint_off -rule UNUSED -file "*/rtl/prim_sparse_fsm_flop.sv" -match "*CustomForceName*"
diff --git a/hw/ip/prim/prim_assert.core b/hw/ip/prim/prim_assert.core
index 12cf78a..26e88e4 100644
--- a/hw/ip/prim/prim_assert.core
+++ b/hw/ip/prim/prim_assert.core
@@ -13,6 +13,7 @@
       - rtl/prim_assert_yosys_macros.svh : {is_include_file : true}
       - rtl/prim_assert_standard_macros.svh : {is_include_file : true}
       - rtl/prim_assert_sec_cm.svh : {is_include_file : true}
+      - rtl/prim_flop_macros.sv : {is_include_file : true}
     file_type: systemVerilogSource
 
   files_verilator_waiver:
diff --git a/hw/ip/prim/rtl/prim_assert.sv b/hw/ip/prim/rtl/prim_assert.sv
index a5b17a8..2521042 100644
--- a/hw/ip/prim/rtl/prim_assert.sv
+++ b/hw/ip/prim/rtl/prim_assert.sv
@@ -141,5 +141,6 @@
 `endif
 
 `include "prim_assert_sec_cm.svh"
+`include "prim_flop_macros.sv"
 
 `endif // PRIM_ASSERT_SV
diff --git a/hw/ip/prim/rtl/prim_flop_macros.sv b/hw/ip/prim/rtl/prim_flop_macros.sv
new file mode 100644
index 0000000..edae459
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_flop_macros.sv
@@ -0,0 +1,74 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`ifndef PRIM_FLOP_MACROS_SV
+`define PRIM_FLOP_MACROS_SV
+
+/////////////////////////////////////
+// Default Values for Macros below //
+/////////////////////////////////////
+
+`define PRIM_FLOP_CLK clk_i
+`define PRIM_FLOP_RST rst_ni
+`define PRIM_FLOP_RESVAL '0
+
+/////////////////////
+// Register Macros //
+/////////////////////
+
+// TODO: define other variations of register macros so that they can be used throughout all designs
+// to make the code more concise.
+
+// Register with asynchronous reset.
+`define PRIM_FLOP_A(__d, __q, __resval = `PRIM_FLOP_RESVAL, __clk = `PRIM_FLOP_CLK, __rst_n = `PRIM_FLOP_RST) \
+  always_ff @(posedge __clk or negedge __rst_n) begin \
+    if (!__rst_n) begin                               \
+      __q <= __resval;                                \
+    end else begin                                    \
+      __q <= __d;                                     \
+    end                                               \
+  end
+
+///////////////////////////
+// Macro for Sparse FSMs //
+///////////////////////////
+
+// Simulation tools typically infer FSMs and report coverage for these separately. However, tools
+// like Xcelium and VCS seem to have problems inferring FSMs if the state register is not coded in
+// a behavioral always_ff block in the same hierarchy. To that end, this uses a modified variant
+// with a second behavioral register definition for RTL simulations so that FSMs can be inferred.
+// Note that in this variant, the __q output is disconnected from prim_sparse_fsm_flop and attached
+// to the behavioral flop. An assertion is added to ensure equivalence between the
+// prim_sparse_fsm_flop output and the behavioral flop output in that case.
+`define PRIM_FLOP_SPARSE_FSM(__name, __d, __q, __type, __resval = `PRIM_FLOP_RESVAL, __clk = `PRIM_FLOP_CLK, __rst_n = `PRIM_FLOP_RST, __alert_trigger_sva_en = 1) \
+  `ifdef SIMULATION                                   \
+    prim_sparse_fsm_flop #(                           \
+      .StateEnumT(__type),                            \
+      .Width($bits(__type)),                          \
+      .ResetValue($bits(__type)'(__resval)),          \
+      .EnableAlertTriggerSVA(__alert_trigger_sva_en), \
+      .CustomForceName(`PRIM_STRINGIFY(__q))          \
+    ) __name (                                        \
+      .clk_i   ( __clk   ),                           \
+      .rst_ni  ( __rst_n ),                           \
+      .state_i ( __d     ),                           \
+      .state_o (         )                            \
+    );                                                \
+    `PRIM_FLOP_A(__d, __q, __resval, __clk, __rst_n)  \
+    `ASSERT(``__name``_A, __q === ``__name``.state_o) \
+  `else                                               \
+    prim_sparse_fsm_flop #(                           \
+      .StateEnumT(__type),                            \
+      .Width($bits(__type)),                          \
+      .ResetValue($bits(__type)'(__resval)),          \
+      .EnableAlertTriggerSVA(__alert_trigger_sva_en)  \
+    ) __name (                                        \
+      .clk_i   ( __clk   ),                           \
+      .rst_ni  ( __rst_n ),                           \
+      .state_i ( __d     ),                           \
+      .state_o ( __q     )                            \
+    );                                                \
+  `endif
+
+`endif // PRIM_FLOP_MACROS_SV
diff --git a/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv b/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv
index a327457..d55db6a 100644
--- a/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv
+++ b/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv
@@ -5,21 +5,29 @@
 `include "prim_assert.sv"
 
 module prim_sparse_fsm_flop #(
-  parameter type              StateEnumT = logic,
   parameter int               Width      = 1,
-  parameter logic [Width-1:0] ResetValue = 0,
+  parameter type              StateEnumT = logic [Width-1:0],
+  parameter logic [Width-1:0] ResetValue = '0,
   // This should only be disabled in special circumstances, for example
   // in non-comportable IPs where an error does not trigger an alert.
   parameter bit               EnableAlertTriggerSVA = 1
+`ifdef SIMULATION
+  ,
+  // In case this parameter is set to a non-empty string, the
+  // prim_sparse_fsm_flop_if will also force the signal with this name
+  // in the parent module that instantiates prim_sparse_fsm_flop.
+  parameter string            CustomForceName = ""
+`endif
 ) (
-  input                    clk_i,
-  input                    rst_ni,
-  input        [Width-1:0] state_i,
-  output logic [Width-1:0] state_o
+  input             clk_i,
+  input             rst_ni,
+  input  StateEnumT state_i,
+  output StateEnumT state_o
 );
 
   logic unused_err_o;
 
+  logic [Width-1:0] state_raw;
   prim_flop #(
     .Width(Width),
     .ResetValue(ResetValue)
@@ -27,15 +35,16 @@
     .clk_i,
     .rst_ni,
     .d_i(state_i),
-    .q_o(state_o)
+    .q_o(state_raw)
   );
+  assign state_o = StateEnumT'(state_raw);
 
   `ifdef INC_ASSERT
   assign unused_err_o = is_undefined_state(state_o);
 
-  function automatic logic is_undefined_state(logic [Width-1:0] sig);
+  function automatic logic is_undefined_state(StateEnumT sig);
     for (int i = 0, StateEnumT t = t.first(); i < t.num(); i += 1, t = t.next()) begin
-      if (StateEnumT'(sig) === t) return 0;
+      if (sig === t) return 0;
     end
     return 1;
   endfunction