[top/dv] Add sensor_ctrl_alerts test

- This test checks that every AST event can be triggered as either a
  recoverable or fatal alert.

Signed-off-by: Timothy Chen <timothytim@google.com>
diff --git a/hw/top_earlgrey/dv/chip_sim_cfg.hjson b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
index a99377a..248fa51 100644
--- a/hw/top_earlgrey/dv/chip_sim_cfg.hjson
+++ b/hw/top_earlgrey/dv/chip_sim_cfg.hjson
@@ -596,6 +596,13 @@
       en_run_modes: ["sw_test_mode_test_rom"]
     }
     {
+      name: chip_sw_sensor_ctrl_alert
+      uvm_test_seq: chip_sw_base_vseq
+      sw_images: ["sw/device/tests/sensor_ctrl_alert_test:1"]
+      en_run_modes: ["sw_test_mode_test_rom"]
+      run_opts: ["+sw_test_timeout_ns=30000000"]
+    }
+    {
       name: chip_sw_pwrmgr_sleep_sensor_ctrl_alert_wakeup
       uvm_test_seq: chip_sw_base_vseq
       sw_images: ["sw/device/tests/sensor_ctrl_wakeup_test:1"]
diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD
index 39cbe87..5ade661 100644
--- a/sw/device/tests/BUILD
+++ b/sw/device/tests/BUILD
@@ -776,6 +776,29 @@
 )
 
 opentitan_functest(
+    name = "sensor_ctrl_alert_test",
+    srcs = ["sensor_ctrl_alerts.c"],
+    deps = [
+        "//hw/top_earlgrey/ip/sensor_ctrl/data:sensor_ctrl_regs",
+        "//hw/top_earlgrey/sw/autogen:top_earlgrey",
+        "//sw/device/lib/arch:device",
+        "//sw/device/lib/base:abs_mmio",
+        "//sw/device/lib/base:memory",
+        "//sw/device/lib/base:mmio",
+        "//sw/device/lib/dif:alert_handler",
+        "//sw/device/lib/dif:rstmgr",
+        "//sw/device/lib/dif:rv_plic",
+        "//sw/device/lib/dif:sensor_ctrl",
+        "//sw/device/lib/runtime:hart",
+        "//sw/device/lib/runtime:ibex",
+        "//sw/device/lib/runtime:log",
+        "//sw/device/lib/runtime:print",
+        "//sw/device/lib/testing:flash_ctrl_testutils",
+        "//sw/device/lib/testing:pwrmgr_testutils",
+    ],
+)
+
+opentitan_functest(
     name = "sensor_ctrl_wakeup_test",
     srcs = ["sensor_ctrl_wakeup.c"],
     deps = [
diff --git a/sw/device/tests/meson.build b/sw/device/tests/meson.build
index db42f03..f493700 100644
--- a/sw/device/tests/meson.build
+++ b/sw/device/tests/meson.build
@@ -919,6 +919,34 @@
   }
 }
 
+sensor_ctrl_alert_test_lib = declare_dependency(
+  link_with: static_library(
+    'sensor_ctrl_alert_test_lib',
+    sources: [
+      hw_ip_sensor_ctrl_reg_h,
+      'sensor_ctrl_alerts.c'],
+    dependencies: [
+      sw_lib_mmio,
+      sw_lib_dif_alert_handler,
+      sw_lib_dif_flash_ctrl,
+      sw_lib_dif_rstmgr,
+      sw_lib_dif_rv_plic,
+      sw_lib_dif_sensor_ctrl,
+      sw_lib_irq,
+      sw_lib_testing_flash_ctrl_testutils,
+      sw_lib_testing_pwrmgr_testutils,
+      sw_lib_runtime_ibex,
+      sw_lib_runtime_log,
+      top_earlgrey,
+    ],
+  ),
+)
+sw_tests += {
+  'sensor_ctrl_alert_test': {
+    'library': sensor_ctrl_alert_test_lib,
+  }
+}
+
 # USB Device Tests
 usbdev_test_lib = declare_dependency(
   link_with: static_library(
diff --git a/sw/device/tests/sensor_ctrl_alerts.c b/sw/device/tests/sensor_ctrl_alerts.c
new file mode 100644
index 0000000..fb8c55d
--- /dev/null
+++ b/sw/device/tests/sensor_ctrl_alerts.c
@@ -0,0 +1,229 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/base/abs_mmio.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/dif/dif_alert_handler.h"
+#include "sw/device/lib/dif/dif_flash_ctrl.h"
+#include "sw/device/lib/dif/dif_rstmgr.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
+#include "sw/device/lib/dif/dif_sensor_ctrl.h"
+#include "sw/device/lib/irq.h"
+#include "sw/device/lib/runtime/ibex.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/device/lib/testing/flash_ctrl_testutils.h"
+#include "sw/device/lib/testing/pwrmgr_testutils.h"
+#include "sw/device/lib/testing/test_framework/ottf.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "sensor_ctrl_regs.h"  // Generated.
+
+/**
+ * This test checks that incoming ast events can be
+ * configured as both recoverable and fatal.
+ * Further, this test checks that recoverable and fatal
+ * events are able to reach their proper alert_handler
+ * destination.
+ *
+ * Since fatal events do not stop firing once asserted,
+ * this test performs a self reset after every fatal
+ * event.  In order to keep track of how far the test
+ * has advanced, a non-volatile counter in flash is
+ * used to track current progress.
+ */
+static dif_rstmgr_t rstmgr;
+static dif_flash_ctrl_state_t flash_ctrl;
+static dif_sensor_ctrl_t sensor_ctrl;
+static dif_alert_handler_t alert_handler;
+
+/**
+ * The events_vector stores the tested events in bit strike fashion.
+ * This means at first the flash word is 0xFFFF_FFFF.
+ * As events are tested, they are marked as 0 in the vector,
+ * so we go from 0xFFFF_FFFF -> 0xFFFF_FFFE -> 0xFFFF_FFFC
+ */
+__attribute__((section(".non_volatile_scratch")))
+const volatile uint32_t events_vector = -1;
+
+/**
+ *  Extracts current event id from the bit-strike counter.
+ */
+static uint32_t event_to_test() {
+  uint32_t addr = (uint32_t)(&events_vector);
+  uint32_t val = abs_mmio_read32(addr);
+
+  for (size_t i = 0; i < 32; ++i) {
+    if (val >> i & 0x1) {
+      return i;
+    }
+  }
+  return -1;
+};
+
+/**
+ *  Increment flash bit-strike counter.
+ */
+static bool incr_flash_cnt(uint32_t tested_idx) {
+  uint32_t addr = (uint32_t)(&events_vector);
+
+  // set the tested bit to 0
+  uint32_t val = abs_mmio_read32(addr) & ~(1 << tested_idx);
+
+  // program the word into flash
+  return flash_ctrl_testutils_write(&flash_ctrl, addr, 0, &val,
+
+                                    kDifFlashCtrlPartitionTypeData, 1);
+};
+
+/**
+ *  Clear event trigger and recoverble status.
+ */
+static void clear_event(uint32_t idx, dif_toggle_t fatal) {
+  CHECK_DIF_OK(dif_sensor_ctrl_set_ast_event_trigger(&sensor_ctrl, idx,
+                                                     kDifToggleDisabled));
+
+  if (!dif_toggle_to_bool(fatal)) {
+    CHECK_DIF_OK(dif_sensor_ctrl_clear_recov_event(&sensor_ctrl, idx));
+  }
+}
+
+static uint32_t get_events(dif_toggle_t fatal) {
+  dif_sensor_ctrl_events_t events = 0;
+  if (dif_toggle_to_bool(fatal)) {
+    CHECK_DIF_OK(dif_sensor_ctrl_get_fatal_events(&sensor_ctrl, &events));
+  } else {
+    CHECK_DIF_OK(dif_sensor_ctrl_get_recov_events(&sensor_ctrl, &events));
+  }
+  return events;
+}
+
+/**
+ *  Check alert cause registers are correctly set
+ */
+static void check_alert_state(dif_toggle_t fatal) {
+  bool fatal_cause = false;
+  bool recov_cause = false;
+
+  CHECK_DIF_OK(dif_alert_handler_alert_is_cause(
+      &alert_handler, kTopEarlgreyAlertIdSensorCtrlFatalAlert, &fatal_cause));
+
+  CHECK_DIF_OK(dif_alert_handler_alert_is_cause(
+      &alert_handler, kTopEarlgreyAlertIdSensorCtrlRecovAlert, &recov_cause));
+
+  if (dif_toggle_to_bool(fatal)) {
+    CHECK(fatal_cause & !recov_cause,
+          "Fatal alert not correctly observed in alert handler");
+  } else {
+    CHECK(recov_cause & !fatal_cause,
+          "Recov alert not correctly observed in alert handler");
+  }
+
+  CHECK_DIF_OK(dif_alert_handler_alert_acknowledge(
+      &alert_handler, kTopEarlgreyAlertIdSensorCtrlRecovAlert));
+  CHECK_DIF_OK(dif_alert_handler_alert_acknowledge(
+      &alert_handler, kTopEarlgreyAlertIdSensorCtrlFatalAlert));
+};
+
+/**
+ *  First configure fatality of the desired event.
+ *  Then trigger the event from sensor_ctrl to ast.
+ *  Next poll for setting of correct events inside sensor_ctrl status.
+ *  When a recoverable event is triggerd, make sure only recoverable
+ *  status is seen, likewise for fatal events.
+ *  Finally, check for correct capture of cause in alert handler.
+ */
+static void test_event(uint32_t idx, dif_toggle_t fatal) {
+  // Configure event fatality
+  CHECK_DIF_OK(dif_sensor_ctrl_set_alert_fatal(&sensor_ctrl, idx, fatal));
+
+  // Trigger event
+  CHECK_DIF_OK(dif_sensor_ctrl_set_ast_event_trigger(&sensor_ctrl, idx,
+                                                     kDifToggleEnabled));
+
+  // wait for events to set
+  IBEX_SPIN_FOR(get_events(fatal) > 0, 1);
+
+  // Check for the event in ast sensor_ctrl
+  // if the event is not set, error
+  CHECK(((get_events(fatal) >> idx) & 0x1) == 1, "Event %d not observed in AST",
+        idx);
+
+  // check the opposite fatality setting, should not be set
+  CHECK(((get_events(!fatal) >> idx) & 0x1) == 0,
+        "Event %d observed in AST when it should not be", idx);
+
+  // clear event trigger
+  clear_event(idx, fatal);
+
+  // check whether alert handler captured the event
+  check_alert_state(fatal);
+};
+
+bool test_main(void) {
+  // Initialize flash_ctrl
+  CHECK_DIF_OK(dif_flash_ctrl_init_state(
+      &flash_ctrl,
+      mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR)));
+
+  // Initialize sensor_ctrl
+  CHECK_DIF_OK(dif_sensor_ctrl_init(
+      mmio_region_from_addr(TOP_EARLGREY_SENSOR_CTRL_BASE_ADDR), &sensor_ctrl));
+
+  // Initialize alert_handler
+  CHECK_DIF_OK(dif_alert_handler_init(
+      mmio_region_from_addr(TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR),
+      &alert_handler));
+
+  CHECK_DIF_OK(dif_rstmgr_init(
+      mmio_region_from_addr(TOP_EARLGREY_RSTMGR_AON_BASE_ADDR), &rstmgr));
+
+  // Enable both recoverable and fatal alerts
+  CHECK_DIF_OK(dif_alert_handler_configure_alert(
+      &alert_handler, kTopEarlgreyAlertIdSensorCtrlRecovAlert,
+      kDifAlertHandlerClassA, kDifToggleEnabled, kDifToggleEnabled));
+  CHECK_DIF_OK(dif_alert_handler_configure_alert(
+      &alert_handler, kTopEarlgreyAlertIdSensorCtrlFatalAlert,
+      kDifAlertHandlerClassA, kDifToggleEnabled, kDifToggleEnabled));
+
+  // First check the flash stored value
+  uint32_t event_idx = event_to_test();
+
+  // Capture the number of events to test
+  uint32_t sensor_ctrl_events = SENSOR_CTRL_PARAM_NUM_ALERT_EVENTS;
+
+  if (event_idx == sensor_ctrl_events) {
+    LOG_INFO("Tested all events");
+    return true;
+  } else {
+    LOG_INFO("Testing event %d", event_idx);
+  }
+
+  // Enable flash access
+  flash_ctrl_testutils_default_region_access(&flash_ctrl,
+                                             /*rd_en*/ true,
+                                             /*prog_en*/ true,
+                                             /*erase_en*/ true,
+                                             /*scramble_en*/ false,
+                                             /*ecc_en*/ false,
+                                             /*he_en*/ false);
+
+  // test recoverable event
+  test_event(event_idx, /*fatal*/ kDifToggleDisabled);
+
+  // test fatal event
+  test_event(event_idx, /*fatal*/ kDifToggleEnabled);
+
+  // increment flash counter to know where we are
+  if (incr_flash_cnt(event_idx)) {
+    LOG_ERROR("Error when incrementing flash counter");
+  }
+
+  // Now request system to reset and test again
+  LOG_INFO("Rebooting system");
+  CHECK_DIF_OK(dif_rstmgr_software_device_reset(&rstmgr));
+  wait_for_interrupt();
+
+  return true;
+}