[dv/alert_handler] Add a ping timeout sequence

This PR adds a ping timeout sequence in alert_handler.
The previous ping_corner_case sequence was moved because it tries to
cross ping request with escalation request.
This new sequence will focus on ping timeout, and the cross case is
covered in prim_esc testbench.

Signed-off-by: Cindy Chen <chencindy@opentitan.org>
diff --git a/hw/dv/sv/alert_esc_agent/esc_monitor.sv b/hw/dv/sv/alert_esc_agent/esc_monitor.sv
index a633e8c..d4883d6 100644
--- a/hw/dv/sv/alert_esc_agent/esc_monitor.sv
+++ b/hw/dv/sv/alert_esc_agent/esc_monitor.sv
@@ -49,7 +49,7 @@
                  !cfg.probe_vif.get_esc_en() &&
                  !under_reset) begin
             @(cfg.vif.monitor_cb);
-            check_esc_resp(.req(req), .is_ping(1));
+            check_esc_resp(.req(req), .is_ping(1), .ping_triggered(1));
             ping_cnter ++;
           end
           if (under_reset) continue;
@@ -64,7 +64,7 @@
             while (!cfg.probe_vif.get_esc_en() &&
                    !(req.esc_handshake_sta inside {EscIntFail, EscRespComplete, EscReceived})) begin
               @(cfg.vif.monitor_cb);
-              check_esc_resp(.req(req), .is_ping(1));
+              check_esc_resp(.req(req), .is_ping(1), .ping_triggered(1));
             end
           end
             // wait a clk cycle to enter the esc_p/n mode
@@ -132,13 +132,19 @@
     end
   endtask : sig_int_fail_thread
 
-  // this task checks if resp_p/n is correct by:
-  // if ping is interrupt by real escalation, abort checking and goes to next expected stage
-  // if it is not a ping_response, it should follow: low -> high .. until esc_p goes low
-  // if it is a ping_response, it should follow: low -> high -> low -> high
-  // if any clock cycle resp_p/n does not match the expected pattern, reset back to "low" state
-  // if any clock cycle resp_p/n are not complement, reset back to "low" state
-  virtual task check_esc_resp(alert_esc_seq_item req, bit is_ping);
+  // This task checks if resp_p/n is correct.
+  //
+  // Check conditions:
+  // - If ping is interrupt by real escalation, abort checking and goes to next expected stage.
+  // - If it is not a ping_response, it should follow: low -> high .. until esc_p goes low.
+  // - If it is a ping_response, it should follow: low -> high -> low -> high.
+  // - If any clock cycle resp_p/n does not match the expected pattern, reset back to "low" state.
+  // - If any clock cycle resp_p/n are not complement, reset back to "low" state.
+  // The `ping_triggered` input is added to cover a corner case when escalation ping has integrity
+  // error and FSM goes back to EscReceived case. Because escalation ping is an one cycle pulse, so
+  // design does not know this is ping request thus stays in this EscReceived case.
+  // TODO: maybe use separate esc and ping enum to avoid adding this `ping_triggered` input.
+  virtual task check_esc_resp(alert_esc_seq_item req, bit is_ping, bit ping_triggered = 0);
     case (req.esc_handshake_sta)
       EscIntFail, EscReceived: begin
         if (cfg.vif.monitor_cb.esc_rx.resp_p !== 0) begin
@@ -146,8 +152,13 @@
           `downcast(req_clone, req.clone());
           req_clone.esc_handshake_sta = EscIntFail;
           alert_esc_port.write(req_clone);
+          `uvm_info("esc_monitor", $sformatf("[%s]: EscReceived has integrity error",
+              req.alert_esc_type.name()), UVM_HIGH)
         end
-        if (!cfg.probe_vif.get_esc_en() && req.esc_handshake_sta == EscIntFail && !is_ping) begin
+        // If there is signal integrity error or it is not the first ping request or escalation
+        // request, stay in this case for one more clock cycle.
+        if (!cfg.probe_vif.get_esc_en() &&
+            (ping_triggered || (req.esc_handshake_sta == EscIntFail && !is_ping))) begin
           req.esc_handshake_sta = EscReceived;
         end else begin
           req.esc_handshake_sta = EscRespHi;
@@ -159,6 +170,8 @@
         end else if (cfg.vif.monitor_cb.esc_rx.resp_p !== 1) begin
           req.esc_handshake_sta = EscIntFail;
           alert_esc_port.write(req);
+          `uvm_info("esc_monitor", $sformatf("[%s]: EscRespHi has integrity error",
+              req.alert_esc_type.name()), UVM_HIGH)
         end else begin
           req.esc_handshake_sta = EscRespLo;
         end
@@ -169,6 +182,8 @@
         end else if (cfg.vif.monitor_cb.esc_rx.resp_p !== 0) begin
           req.esc_handshake_sta = EscIntFail;
           alert_esc_port.write(req);
+          `uvm_info("esc_monitor", $sformatf("[%s]: EscRespLow has integrity error",
+              req.alert_esc_type.name()), UVM_HIGH)
         end else begin
           if (is_ping) req.esc_handshake_sta = EscRespPing0;
           else req.esc_handshake_sta = EscRespHi;
@@ -180,6 +195,8 @@
         end else if (cfg.vif.monitor_cb.esc_rx.resp_p !== 1) begin
           req.esc_handshake_sta = EscIntFail;
           alert_esc_port.write(req);
+          `uvm_info("esc_monitor", $sformatf("[%s]: EscRespPing0 has integrity error",
+              req.alert_esc_type.name()), UVM_HIGH)
         end else begin
           req.esc_handshake_sta = EscRespPing1;
         end
@@ -190,6 +207,8 @@
         end else if (cfg.vif.monitor_cb.esc_rx.resp_p !== 0) begin
           req.esc_handshake_sta = EscIntFail;
           alert_esc_port.write(req);
+          `uvm_info("esc_monitor", $sformatf("[%s]: EscRespPing1 has integrity error",
+              req.alert_esc_type.name()), UVM_HIGH)
         end else begin
           req.esc_handshake_sta = EscRespComplete;
         end
diff --git a/hw/ip/alert_handler/data/alert_handler_testplan.hjson b/hw/ip/alert_handler/data/alert_handler_testplan.hjson
index 0a1a9d0..3b61b19 100644
--- a/hw/ip/alert_handler/data/alert_handler_testplan.hjson
+++ b/hw/ip/alert_handler/data/alert_handler_testplan.hjson
@@ -81,6 +81,20 @@
       tests: ["alert_handler_random_classes"]
     }
     {
+      name: ping_timeout
+      desc: '''
+            Based on entropy test, this test request alert_sender and esc_receiver drivers to
+            randomly create ping requests timeout stimulus.
+
+            Checks:
+            - Verify interrupt pin and states.
+            - Verify alert and local alert causes.
+            - Verify escalation states and counts.
+            '''
+      milestone: V2
+      tests: ["alert_handler_ping_timeout"]
+    }
+    {
       name: stress_all
       desc: '''
             Combine above sequences in one test to run sequentially with the following exclusions:
diff --git a/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson b/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson
index f06ee05..364ef96 100644
--- a/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson
+++ b/hw/ip/alert_handler/dv/alert_handler_generic_sim_cfg.hjson
@@ -82,6 +82,11 @@
     }
 
     {
+      name: alert_handler_ping_timeout
+      uvm_test_seq: alert_handler_ping_timeout_vseq
+    }
+
+    {
       name: alert_handler_stress_all
       run_opts: ["+test_timeout_ns=15_000_000_000"]
     }
diff --git a/hw/ip/alert_handler/dv/env/alert_handler_env.core b/hw/ip/alert_handler/dv/env/alert_handler_env.core
index 7810828..c29d53c 100644
--- a/hw/ip/alert_handler/dv/env/alert_handler_env.core
+++ b/hw/ip/alert_handler/dv/env/alert_handler_env.core
@@ -26,6 +26,7 @@
       - seq_lib/alert_handler_esc_alert_accum_vseq.sv: {is_include_file: true}
       - seq_lib/alert_handler_sig_int_fail_vseq.sv: {is_include_file: true}
       - seq_lib/alert_handler_entropy_vseq.sv: {is_include_file: true}
+      - seq_lib/alert_handler_ping_timeout_vseq.sv: {is_include_file: true}
       - seq_lib/alert_handler_stress_all_vseq.sv: {is_include_file: true}
     file_type: systemVerilogSource
 
diff --git a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_base_vseq.sv b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_base_vseq.sv
index dc9fb75..5d86ed9 100644
--- a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_base_vseq.sv
+++ b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_base_vseq.sv
@@ -268,7 +268,7 @@
 
   virtual task wr_ping_timeout_cycle(bit[TL_DW-1:0] timeout_val);
     csr_wr(.ptr(ral.ping_timeout_cyc_shadowed), .value(timeout_val));
-    if (!config_locked) begin
+    if (`gmv(ral.ping_timer_regwen)) begin
       if (timeout_val == 0) timeout_val = 1;
       foreach (cfg.alert_host_cfg[i]) cfg.alert_host_cfg[i].ping_timeout_cycle = timeout_val;
       foreach (cfg.esc_device_cfg[i]) cfg.esc_device_cfg[i].ping_timeout_cycle = timeout_val;
diff --git a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_ping_timeout_vseq.sv b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_ping_timeout_vseq.sv
new file mode 100644
index 0000000..c83a131
--- /dev/null
+++ b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_ping_timeout_vseq.sv
@@ -0,0 +1,69 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// this sequence test corner cases for alert or escalation pings:
+// 1). ping integrity fail or timeout
+// 2). ping interrupted by a reset signal
+// 3). escalation ping interrupted by real escalation signal (this could happen because escalation
+//     ping and real escalation share the same esc_p/n signals)
+
+class alert_handler_ping_timeout_vseq extends alert_handler_entropy_vseq;
+  `uvm_object_utils(alert_handler_ping_timeout_vseq)
+
+  `uvm_object_new
+
+  constraint num_trans_c {
+    num_trans inside {[1:10]};
+  }
+
+  constraint alert_trigger_c {
+    alert_trigger == 0;
+  }
+
+  constraint intr_en_c {
+    intr_en == '1;
+  }
+
+  constraint sig_int_c {
+    alert_int_err          == 0;
+    esc_int_err            == 0;
+    esc_standalone_int_err == 0;
+  }
+
+  constraint loc_alert_en_c {
+    local_alert_en[LocalEscPingFail:LocalAlertPingFail] > 0;
+  }
+
+  constraint ping_fail_c {
+    alert_ping_timeout == '1;
+    esc_ping_timeout   == '1;
+  }
+
+  constraint ping_timeout_cyc_c {
+    ping_timeout_cyc inside {[1:MAX_PING_TIMEOUT_CYCLE]};
+  }
+
+  // disable interrupt timeout
+  constraint esc_intr_timeout_c {
+    foreach (intr_timeout_cyc[i]) {intr_timeout_cyc[i] == 0;}
+  }
+
+  function void pre_randomize();
+    this.enable_classa_only_c.constraint_mode(0);
+  endfunction
+
+  // In this sequence, because we disable all external alerts, so to ensure local alerts are
+  // triggerd, we wait for interrupt pins to fire then wait for alert and escalation handshake
+  // to finish.
+  virtual task wait_alert_esc_done();
+    wait (cfg.intr_vif.pins[NUM_ALERT_CLASSES-1:0]);
+    // Wait two clock cycles to avoid building a cycle-accurate scb.
+    cfg.clk_rst_vif.wait_clks(2);
+    `uvm_info(`gfn, $sformatf("Interrupt pin = %0h", cfg.intr_vif.pins[NUM_ALERT_CLASSES-1:0]),
+              UVM_LOW)
+    check_alert_interrupts();
+    super.wait_alert_esc_done();
+  endtask
+
+endclass : alert_handler_ping_timeout_vseq
diff --git a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_smoke_vseq.sv b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_smoke_vseq.sv
index c1aea42..2ff03b9 100644
--- a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_smoke_vseq.sv
+++ b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_smoke_vseq.sv
@@ -163,15 +163,12 @@
       if ((esc_int_err == 0) && (esc_ping_timeout == 0)) check_alert_interrupts();
 
       // if ping timeout enabled, wait for ping timeout done before checking escalation phases
-      if ((esc_int_err | alert_ping_timeout) > 0) cfg.clk_rst_vif.wait_clks(MAX_PING_TIMEOUT_CYCLE);
+      if ((esc_int_err | alert_ping_timeout) > 0) begin
+        cfg.clk_rst_vif.wait_clks(MAX_PING_TIMEOUT_CYCLE);
+      end
 
       // wait escalation done, and random interrupt with clear_esc
-      wait_alert_handshake_done();
-      if ($urandom_range(0, 1) && (esc_int_err == 0)) begin
-        cfg.clk_rst_vif.wait_clks($urandom_range(0, max_wait_phases_cyc));
-        clear_esc();
-      end
-      wait_esc_handshake_done();
+      wait_alert_esc_done();
 
       read_alert_cause();
       read_esc_status();
@@ -180,4 +177,13 @@
     end
   endtask
 
+  virtual task wait_alert_esc_done();
+    wait_alert_handshake_done();
+    if ($urandom_range(0, 1) && (esc_int_err == 0)) begin
+      cfg.clk_rst_vif.wait_clks($urandom_range(0, max_wait_phases_cyc));
+      clear_esc();
+    end
+    wait_esc_handshake_done();
+  endtask
+
 endclass : alert_handler_smoke_vseq
diff --git a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_vseq_list.sv b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_vseq_list.sv
index cede15e..14b176f 100644
--- a/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_vseq_list.sv
+++ b/hw/ip/alert_handler/dv/env/seq_lib/alert_handler_vseq_list.sv
@@ -11,4 +11,5 @@
 `include "alert_handler_esc_alert_accum_vseq.sv"
 `include "alert_handler_sig_int_fail_vseq.sv"
 `include "alert_handler_entropy_vseq.sv"
+`include "alert_handler_ping_timeout_vseq.sv"
 `include "alert_handler_stress_all_vseq.sv"