[prim] Add flop wrapper for sparse fsm

- Implements proposal from #9043
- The flop module used for anchoring the sparse state is
  now wrapped another layer for streamlining DV.
- This follows the prim_count scheme very closely and should
  standardize the alert hook-up testing for all modules.

- Once this PR is approved, similar changes will be made to all
  blocks that utilize sparse FSM.

Signed-off-by: Timothy Chen <timothytim@google.com>
diff --git a/hw/ip/keymgr/keymgr.core b/hw/ip/keymgr/keymgr.core
index c1a0a4f..358b6cc 100644
--- a/hw/ip/keymgr/keymgr.core
+++ b/hw/ip/keymgr/keymgr.core
@@ -8,17 +8,18 @@
 filesets:
   files_rtl:
     depend:
-      - lowrisc:ip:tlul
       - lowrisc:prim:all
       - lowrisc:prim:count
-      - lowrisc:prim:lfsr
       - lowrisc:prim:lc_sync
+      - lowrisc:prim:lfsr
       - lowrisc:prim:msb_extend
+      - lowrisc:prim:sparse_fsm
       - lowrisc:ip:flash_ctrl_pkg
       - lowrisc:ip:keymgr_pkg
       - lowrisc:ip:kmac_pkg
       - lowrisc:ip:otp_ctrl_pkg
       - lowrisc:ip:rom_ctrl_pkg
+      - lowrisc:ip:tlul
     files:
       - rtl/keymgr_reg_top.sv
       - rtl/keymgr_sideload_key_ctrl.sv
diff --git a/hw/ip/keymgr/rtl/keymgr.sv b/hw/ip/keymgr/rtl/keymgr.sv
index bb83ec1..0598965 100644
--- a/hw/ip/keymgr/rtl/keymgr.sv
+++ b/hw/ip/keymgr/rtl/keymgr.sv
@@ -657,4 +657,5 @@
   `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(KmacIfCntAlertCheck_A, u_kmac_if.u_cnt, alert_tx_o[0])
   `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(ReseedCtrlCntAlertCheck_A, u_reseed_ctrl.u_reseed_cnt,
                                          alert_tx_o[0])
+  `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CtrlMainFsmCheck_A, u_ctrl.u_state_regs, alert_tx_o[0])
 endmodule // keymgr
diff --git a/hw/ip/keymgr/rtl/keymgr_ctrl.sv b/hw/ip/keymgr/rtl/keymgr_ctrl.sv
index a1af5c8..abf4bbd 100644
--- a/hw/ip/keymgr/rtl/keymgr_ctrl.sv
+++ b/hw/ip/keymgr/rtl/keymgr_ctrl.sv
@@ -236,17 +236,17 @@
   //////////////////////////
   // Main Control FSM
   //////////////////////////
-
   logic [StateWidth-1:0] state_raw_q;
   assign state_q = keymgr_ctrl_state_e'(state_raw_q);
-  prim_flop #(
+  prim_sparse_fsm_flop #(
+    .StateEnumT(keymgr_ctrl_state_e),
     .Width(StateWidth),
     .ResetValue(StateWidth'(StCtrlReset))
   ) u_state_regs (
     .clk_i,
     .rst_ni,
-    .d_i ( state_d     ),
-    .q_o ( state_raw_q )
+    .state_i ( state_d     ),
+    .state_o ( state_raw_q )
   );
 
   always_ff @(posedge clk_i or negedge rst_ni) begin
diff --git a/hw/ip/prim/prim_sparse_fsm.core b/hw/ip/prim/prim_sparse_fsm.core
new file mode 100644
index 0000000..5367085
--- /dev/null
+++ b/hw/ip/prim/prim_sparse_fsm.core
@@ -0,0 +1,37 @@
+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:prim:sparse_fsm"
+description: ""
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:prim:assert
+    files:
+      - rtl/prim_sparse_fsm_flop.sv
+    file_type: systemVerilogSource
+
+  files_verilator_waiver:
+    depend:
+      # common waivers
+      - lowrisc:lint:common
+
+  files_ascentlint_waiver:
+    depend:
+      # common waivers
+      - lowrisc:lint:common
+
+  files_veriblelint_waiver:
+    depend:
+      # common waivers
+      - lowrisc:lint:common
+
+targets:
+  default:
+    filesets:
+      - tool_verilator   ? (files_verilator_waiver)
+      - tool_ascentlint  ? (files_ascentlint_waiver)
+      - tool_veriblelint ? (files_veriblelint_waiver)
+      - files_rtl
diff --git a/hw/ip/prim/rtl/prim_lc_sync.sv b/hw/ip/prim/rtl/prim_lc_sync.sv
index d575674..b61343c 100644
--- a/hw/ip/prim/rtl/prim_lc_sync.sv
+++ b/hw/ip/prim/rtl/prim_lc_sync.sv
@@ -30,7 +30,7 @@
   output lc_ctrl_pkg::lc_tx_t [NumCopies-1:0] lc_en_o
 );
 
-  localparam lc_ctrl_pkg::lc_tx_t ResetValue = (ResetValueIsOn) ? lc_ctrl_pkg::On :
+  localparam lc_ctrl_pkg::lc_tx_t LcResetValue = (ResetValueIsOn) ? lc_ctrl_pkg::On :
                                                                   lc_ctrl_pkg::Off;
 
   `ASSERT_INIT(NumCopiesMustBeGreaterZero_A, NumCopies > 0)
@@ -39,7 +39,7 @@
   if (AsyncOn) begin : gen_flops
     prim_flop_2sync #(
       .Width(lc_ctrl_pkg::TxWidth),
-      .ResetValue(lc_ctrl_pkg::TxWidth'(ResetValue))
+      .ResetValue(lc_ctrl_pkg::TxWidth'(LcResetValue))
     ) u_prim_flop_2sync (
       .clk_i,
       .rst_ni,
diff --git a/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv b/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv
new file mode 100644
index 0000000..ab29d7c
--- /dev/null
+++ b/hw/ip/prim/rtl/prim_sparse_fsm_flop.sv
@@ -0,0 +1,55 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`include "prim_assert.sv"
+
+module prim_sparse_fsm_flop #(
+  parameter type              StateEnumT = logic,
+  parameter int               Width      = 1,
+  parameter logic [Width-1:0] ResetValue = 0
+) (
+  input                    clk_i,
+  input                    rst_ni,
+  input        [Width-1:0] state_i,
+  output logic [Width-1:0] state_o
+);
+
+  logic unused_valid_st;
+
+  prim_flop #(
+    .Width(Width),
+    .ResetValue(ResetValue)
+  ) u_state_flop (
+    .clk_i,
+    .rst_ni,
+    .d_i(state_i),
+    .q_o(state_o)
+  );
+
+  `ifdef INC_ASSERT
+    StateEnumT tmp;
+    assign unused_valid_st = $cast(tmp, state_o);
+  `else
+    assign unused_valid_st = 1'b1;
+  `endif
+
+  // If ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT is declared, the unused_assert_connected signal will
+  // be set to 1 and the below check will pass.
+  // If the assertion is not declared however, the statement below will fail.
+  `ifdef INC_ASSERT
+  logic unused_assert_connected;
+
+  // ASSERT_INIT can only be used for paramters/constants in FPV.
+  `ifdef SIMULATION
+  `ASSERT_INIT(AssertConnected_A, unused_assert_connected === 1'b1)
+  `endif
+  `endif
+
+endmodule
+
+`define ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(NAME_, PRIM_HIER_, ALERT_, MAX_CYCLES_ = 5) \
+  `ASSERT(NAME_, $fell(PRIM_HIER_.unused_valid_st) |-> ##[1:MAX_CYCLES_] $rose(ALERT_.alert_p)) \
+  `ifdef INC_ASSERT \
+  assign PRIM_HIER_.unused_assert_connected = 1'b1; \
+  `endif
diff --git a/util/design/sparse-fsm-encode.py b/util/design/sparse-fsm-encode.py
index 22f31c5..fc7817f 100755
--- a/util/design/sparse-fsm-encode.py
+++ b/util/design/sparse-fsm-encode.py
@@ -239,14 +239,15 @@
 // flops in order to prevent FSM state encoding optimizations.
 logic [StateWidth-1:0] state_raw_q;
 assign state_q = state_e'(state_raw_q);
-prim_flop #(
+prim_sparse_fsm_flop #(
+  .StateEnumT(state_e),
   .Width(StateWidth),
   .ResetValue(StateWidth'(State0))
 ) u_state_regs (
   .clk_i,
   .rst_ni,
-  .d_i ( state_d     ),
-  .q_o ( state_raw_q )
+  .state_i ( state_d     ),
+  .state_o ( state_raw_q )
 );
 '''.format(state_str))