[dv/prim_esc] Direct test for prim_rx/tx

This PR writes a direct test for prim_rx/tx pairs to cover the following
checkpoints:
1). Escalation request hanshake.
2). Escalation esc_tx integrity error.
3). Escalation reverse ping timeout.
4). Escalation counter fail.

Signed-off-by: Cindy Chen <chencindy@opentitan.org>
diff --git a/hw/ip/prim/dv/prim_esc/prim_esc_sim.core b/hw/ip/prim/dv/prim_esc/prim_esc_sim.core
new file mode 100644
index 0000000..ef00ab6
--- /dev/null
+++ b/hw/ip/prim/dv/prim_esc/prim_esc_sim.core
@@ -0,0 +1,31 @@
+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:dv:prim_esc_sim:0.1"
+description: "ESCALATOR DV sim target"
+filesets:
+  files_rtl:
+    depend:
+      - lowrisc:prim:esc
+    file_type: systemVerilogSource
+
+  files_dv:
+    depend:
+      - lowrisc:dv:dv_utils
+      - lowrisc:dv:dv_test_status
+      - lowrisc:dv:common_ifs
+    files:
+      - tb/prim_esc_tb.sv
+    file_type: systemVerilogSource
+
+targets:
+  sim: &sim_target
+    toplevel: prim_esc_tb
+    filesets:
+      - files_rtl
+      - files_dv
+    default_tool: vcs
+
+  lint:
+    <<: *sim_target
diff --git a/hw/ip/prim/dv/prim_esc/prim_esc_sim_cfg.hjson b/hw/ip/prim/dv/prim_esc/prim_esc_sim_cfg.hjson
new file mode 100644
index 0000000..ba53aac
--- /dev/null
+++ b/hw/ip/prim/dv/prim_esc/prim_esc_sim_cfg.hjson
@@ -0,0 +1,41 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+{
+  // Name of the sim cfg - typically same as the name of the DUT.
+  name: prim_esc
+
+  // Top level dut name (sv module).
+  dut: prim_esc
+
+  // Top level testbench name (sv module).
+  tb: prim_esc_tb
+
+  // Simulator used to sign off this block
+  tool: vcs
+
+  // Fusesoc core file used for building the file list.
+  fusesoc_core: lowrisc:dv:prim_esc_sim:0.1
+
+  // Import additional common sim cfg files.
+  import_cfgs: ["{proj_root}/hw/dv/tools/dvsim/common_sim_cfg.hjson"]
+
+  // Default iterations for all tests - each test entry can override this.
+  reseed: 1
+
+  // List of test specifications.
+  tests: [
+    {
+      name: prim_esc_test
+      run_opts: ["+test_timeout_ns=1_000"]
+    }
+  ]
+
+  // List of regressions.
+  regressions: [
+    {
+      name: smoke
+      tests: ["prim_esc_test"]
+    }
+  ]
+}
diff --git a/hw/ip/prim/dv/prim_esc/tb/prim_esc_tb.sv b/hw/ip/prim/dv/prim_esc/tb/prim_esc_tb.sv
new file mode 100644
index 0000000..5264b8d
--- /dev/null
+++ b/hw/ip/prim/dv/prim_esc/tb/prim_esc_tb.sv
@@ -0,0 +1,166 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Testbench module for prim_esc_sender and prim_esc_receiver_pair.
+//
+// This test has four sequnces:
+// 1). Escalation request sequence:
+//     Drive `esc_req` signal to send an escalation request.
+//     Wait random length of cycles and check `esc_en` and `integ_fail` signals.
+//     Drive `esc_req` back to zero.
+// 2). Esc_tx integrity error sequence:
+//     Drive `esc_req` signal to send an escalation request.
+//     Force `esc_tx.esc_n` to stay high to create signal integrity errors.
+//     Wait for `integ_fail` to fire then release the forced signal.
+//     Drive `esc_req` back to zero.
+// 3). Escalation reverse ping request timeout sequence:
+//     Drive `ping_req` signal to send an escalation ping request.
+//     Wait for ping handshake to finish by waiting for `ping_ok` signal to set.
+//     Drive `ping_req` signal back to 0 and check `esc_en` and `integ_fail` signals.
+//     In the meantime, wait until ping counter timeout for next ping request, and check if
+//     `esc_en` is set.
+//     Reset DUT to clear `esc_en`.
+// 4). Escalation receiver counter fail sequence:
+//     Drive `ping_req` signal to send an escalation ping request.
+//     Wait until `ping_ok` to ensure the ping counter starts to increment.
+//     Force one of the two identical counters to 0 and wait for `esc_en` to set.
+
+module prim_esc_tb;
+
+  //////////////////////////////////////////////////////
+  // config
+  //////////////////////////////////////////////////////
+
+  localparam time ClkPeriod  = 10000;
+  localparam int  PING_CNT_DW = 0;
+  localparam int  TIMEOUT_CYCLES = 1 << (PING_CNT_DW + 6);
+
+  //////////////////////////////////////////////////////
+  // Clock and Reset
+  //////////////////////////////////////////////////////
+
+  wire clk, rst_n;
+
+  clk_rst_if main_clk (
+    .clk,
+    .rst_n
+  );
+
+  //////////////////////////////////////////////////////
+  // DUTs
+  //////////////////////////////////////////////////////
+
+  logic ping_req, ping_ok, integ_fail, esc_req, esc_en;
+  prim_esc_pkg::esc_tx_t esc_tx;
+  prim_esc_pkg::esc_rx_t esc_rx;
+
+  prim_esc_sender i_esc_sender (
+    .clk_i(clk),
+    .rst_ni(rst_n),
+    .ping_req_i(ping_req),
+    .ping_ok_o(ping_ok),
+    .integ_fail_o(integ_fail),
+    .esc_req_i(esc_req),
+    .esc_rx_i(esc_rx),
+    .esc_tx_o(esc_tx)
+  );
+
+  prim_esc_receiver #(
+    .N_ESC_SEV(4),
+    // Set to 0 to avoid long wait period to check ping request reverse timeout.
+    .PING_CNT_DW(PING_CNT_DW)
+  ) i_esc_receiver (
+    .clk_i(clk),
+    .rst_ni(rst_n),
+    .esc_en_o(esc_en),
+    .esc_rx_o(esc_rx),
+    .esc_tx_i(esc_tx)
+  );
+
+  //////////////////////////////////////////////////////
+  // Helper Functions/Tasks and Variables
+  //////////////////////////////////////////////////////
+
+  logic error = 0;
+
+  function automatic void test_error(string msg);
+    $error(msg);
+    error = 1;
+  endfunction
+
+  //////////////////////////////////////////////////////
+  // Stimuli Application / Response Checking
+  //////////////////////////////////////////////////////
+
+  initial begin: p_stimuli
+    ping_req = 0;
+    esc_req  = 0;
+    main_clk.set_period_ps(ClkPeriod);
+    main_clk.set_active();
+    main_clk.apply_reset();
+
+    // 1). Escalation request sequence.
+    esc_req = 1;
+    // Drive random length of esc_req and check `esc_en` and `integ_fail` outputs.
+    main_clk.wait_clks($urandom_range(1, 20));
+    if (integ_fail == 1) test_error("Esc_req unexpected signal integrity error!");
+    if (esc_en == 0)     test_error("Esc_req did not set esc_en!");
+    esc_req = 0;
+
+    $display("Escalation request sequence passed!");
+
+    // 2). Esc_tx integrity error sequence.
+    main_clk.wait_clks($urandom_range(1, 20));
+    esc_req = 1;
+    // Force esc_tx signal to create a integrity fail error case.
+    force esc_tx.esc_n = 1;
+    wait (integ_fail == 1);
+    release esc_tx.esc_n;
+    if (esc_en == 1) test_error("Signal integrity error should not set esc_en!");
+    esc_req = 0;
+
+    $display("Escalation esc_p/n integrity sequence passed!");
+
+    // 3). Escalation reverse ping request timeout sequence.
+    main_clk.wait_clks($urandom_range(0, 20));
+    ping_req = 1;
+    fork
+      begin
+        // After one ping_req, esc_receiver will start a counter to expect next ping_req. If the
+        // counter reaches its max value but no ping_req has been received, design will set
+        // `esc_en_o` signal.
+        main_clk.wait_clks(TIMEOUT_CYCLES + 1);
+        if (esc_en != 1) test_error("Design failed to detect ping request timeout!");
+      end
+      begin
+        // Wait for a ping handshake to complete.
+        wait (ping_ok == 1);
+        ping_req = 0;
+        if (integ_fail == 1) test_error("Ping_req unexpected signal integrity error!");
+        if (esc_en == 1)     test_error("Ping request should not set esc_en!");
+      end
+    join
+    main_clk.apply_reset();
+
+    $display("Escalation ping request timeout sequence passed!");
+
+    // 4). Escalation receiver counter fail sequence.
+    ping_req = 1;
+    // Wait until ping request is acknowledged and counter starts to increment.
+    wait (ping_ok == 1);
+    ping_req = 0;
+    // If cnt_q[0] and cnt_q[1]'s value do not match, deisgn will set `esc_en_o` signal.
+    force prim_esc_tb.i_esc_receiver.cnt_q[1] = 0;
+    wait (esc_en == 1);
+    if (integ_fail == 1) begin
+      test_error("Escalation receiver counter unexpected signal integrity error!");
+    end
+
+    $display("Escalation couter error sequence passed!");
+
+    dv_test_status_pkg::dv_test_status(.passed(!error));
+    $finish();
+  end
+
+endmodule