Replace polling with irq handling for ML core processing

Bug:284028282

Change-Id: I8b826aa17bac5d16edd799a8c0888a59813632fe
diff --git a/sw/device/examples/demo_hps_from_test_images/BUILD b/sw/device/examples/demo_hps_from_test_images/BUILD
index 454138a..04715b6 100644
--- a/sw/device/examples/demo_hps_from_test_images/BUILD
+++ b/sw/device/examples/demo_hps_from_test_images/BUILD
@@ -132,6 +132,8 @@
     deps = [
         "//hw/top_matcha/ip/ml_top/data:ml_top_regs",
         "//hw/top_matcha/sw/autogen:top_matcha",
+        "//sw/device/lib/dif:ml_top",
+        "//sw/device/lib/dif:rv_plic_smc",
         "//sw/device/tests:test_lib_smc",
         "@lowrisc_opentitan//sw/device/silicon_creator/lib:manifest_def",
     ],
@@ -155,6 +157,7 @@
         "//hw/top_matcha/ip/ml_top/data:ml_top_regs",
         "//hw/top_matcha/sw/autogen:top_matcha",
         "//sw/device/lib/dif:ml_top",
+        "//sw/device/lib/dif:rv_plic_smc",
         "//sw/device/tests:test_lib_smc",
         "@lowrisc_opentitan//sw/device/silicon_creator/lib:manifest_def",
     ],
diff --git a/sw/device/examples/demo_hps_from_test_images/kelvin_checksum_smc.c b/sw/device/examples/demo_hps_from_test_images/kelvin_checksum_smc.c
index aac3b8e..2b34a47 100644
--- a/sw/device/examples/demo_hps_from_test_images/kelvin_checksum_smc.c
+++ b/sw/device/examples/demo_hps_from_test_images/kelvin_checksum_smc.c
@@ -18,7 +18,10 @@
 #include "hw/top_matcha/sw/autogen/top_matcha.h"
 #include "sw/device/examples/testdata/hps_images/hps_images.h"  // Generated.
 #include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_ml_top.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
 #include "sw/device/lib/dif/dif_uart.h"
+#include "sw/device/lib/runtime/irq.h"
 #include "sw/device/lib/runtime/print.h"
 #include "sw/device/lib/testing/test_framework/check.h"
 #include "sw/device/lib/testing/test_framework/ottf_test_config.h"
@@ -31,13 +34,97 @@
 
 OTTF_DEFINE_TEST_CONFIG();
 
+static dif_ml_top_t ml_top;
+static dif_rv_plic_t plic_smc;
 static dif_uart_t smc_uart;
 
+static volatile bool ml_top_finish_done = false;
+
+static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
+  switch (interrupt_id) {
+    case kTopMatchaPlicIrqIdMlTopFinish:
+      ml_top_finish_done = true;
+      break;
+    case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
+      LOG_ERROR("ML core raised fault interrupt.");
+      test_status_set(kTestStatusFailed);
+    default:
+      LOG_FATAL("ISR is not implemented!");
+      test_status_set(kTestStatusFailed);
+  }
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
+void ottf_external_isr(void) {
+  // Claim the IRQ by reading the Ibex specific CC register.
+  dif_rv_plic_irq_id_t interrupt_id;
+
+  CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                     &interrupt_id));
+
+  // Check if the interrupted peripheral is ISP WRAPPER.
+  top_matcha_plic_peripheral_smc_t peripheral_id =
+      top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id];
+  CHECK(peripheral_id == kTopMatchaPlicPeripheralMlTop,
+        "Unexpected peripheral in ISR: %d", peripheral_id);
+  switch (peripheral_id) {
+    case kTopMatchaPlicPeripheralMlTop: {
+      handle_ml_top_isr(interrupt_id);
+      break;
+    }
+    default:
+      LOG_FATAL("Peripheral is not implemented!");
+  }
+
+  // Complete the IRQ by writing the IRQ source to the Ibex specific CC
+  // register.
+  CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                        interrupt_id));
+}
+
+// Configures all relevant interrupts in PLIC_SMC.
+static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
+  // Set IRQ priorities to MAX
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                            kDifRvPlicMaxPriority));
+
+  // Set Ibex IRQ priority threshold level
+  CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
+      plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
+
+  // Enable ML core IRQs
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+}
+
 void _ottf_main(void) {
   test_status_set(kTestStatusInTest);
   init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart);
   LOG_INFO("[SMC] Start from SMC!");
 
+  // Init IRQs
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc));
+  plic_smc_configure_irqs(&plic_smc);
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+
+  // Init ML_TOP
+  mmio_region_t base_addr =
+      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
+  CHECK_DIF_OK(dif_ml_top_init(base_addr, &ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
+                                          kDifToggleEnabled));
+
   // Create an array with the pointers to the image frame
   // Cast the type from unsigned char into uint32_t
   const uint32_t * const hps_image_frame[] = {(const uint32_t *) hps_0,
@@ -78,9 +165,7 @@
   mmio_region_t ml_hps_base =
       mmio_region_from_addr(TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR +
                             TOP_MATCHA_RAM_ML_DMEM_IMG_OFFSET_ADDR);
-  mmio_region_t ml_core =
-      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
-  mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET, ML_TOP_CTRL_REG_RESVAL);
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
 
   // Run ML models and verify HPS images.
   for (int frame_idx = 0; frame_idx < num_of_frames; ++frame_idx) {
@@ -98,16 +183,11 @@
     }
 
     // Start up Kelvin.
-    mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET, 0x0);
-    uint32_t intr_state =
-        mmio_region_read32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET);
-    while (intr_state == 0x0) {
-      intr_state = mmio_region_read32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET);
-      busy_spin_micros(10); // Wait for 10ms.
+    ml_top_finish_done = false;
+    CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
+    while (!ml_top_finish_done) {
+      asm volatile("wfi");
     }
-    mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET,
-                        ML_TOP_CTRL_REG_RESVAL);
-    mmio_region_write32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET, intr_state);
 
     // Verify checksum
     mmio_region_t ml_checksum_base =
diff --git a/sw/device/examples/demo_hps_from_test_images/kelvin_model_smc.c b/sw/device/examples/demo_hps_from_test_images/kelvin_model_smc.c
index 901ff68..3aa087b 100644
--- a/sw/device/examples/demo_hps_from_test_images/kelvin_model_smc.c
+++ b/sw/device/examples/demo_hps_from_test_images/kelvin_model_smc.c
@@ -21,7 +21,9 @@
 #include "sw/device/examples/testdata/kelvin_model_ml_bin.h"    // Generated.
 #include "sw/device/lib/arch/device.h"
 #include "sw/device/lib/dif/dif_ml_top.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
 #include "sw/device/lib/dif/dif_uart.h"
+#include "sw/device/lib/runtime/irq.h"
 #include "sw/device/lib/runtime/print.h"
 #include "sw/device/lib/testing/test_framework/check.h"
 #include "sw/device/lib/testing/test_framework/ottf_test_config.h"
@@ -34,15 +36,95 @@
 
 OTTF_DEFINE_TEST_CONFIG();
 
-static dif_uart_t smc_uart;
 static dif_ml_top_t ml_top;
+static dif_rv_plic_t plic_smc;
+static dif_uart_t smc_uart;
+
+static volatile bool ml_top_finish_done = false;
+
+static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
+  switch (interrupt_id) {
+    case kTopMatchaPlicIrqIdMlTopFinish:
+      ml_top_finish_done = true;
+      break;
+    case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
+      LOG_ERROR("ML core raised fault interrupt.");
+      test_status_set(kTestStatusFailed);
+    default:
+      LOG_FATAL("ISR is not implemented!");
+      test_status_set(kTestStatusFailed);
+  }
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
+void ottf_external_isr(void) {
+  // Claim the IRQ by reading the Ibex specific CC register.
+  dif_rv_plic_irq_id_t interrupt_id;
+
+  CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                     &interrupt_id));
+
+  // Check if the interrupted peripheral is ISP WRAPPER.
+  top_matcha_plic_peripheral_smc_t peripheral_id =
+      top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id];
+  CHECK(peripheral_id == kTopMatchaPlicPeripheralMlTop,
+        "Unexpected peripheral in ISR: %d", peripheral_id);
+  switch (peripheral_id) {
+    case kTopMatchaPlicPeripheralMlTop: {
+      handle_ml_top_isr(interrupt_id);
+      break;
+    }
+    default:
+      LOG_FATAL("Peripheral is not implemented!");
+  }
+
+  // Complete the IRQ by writing the IRQ source to the Ibex specific CC
+  // register.
+  CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                        interrupt_id));
+}
+
+// Configures all relevant interrupts in PLIC_SMC.
+static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
+  // Set IRQ priorities to MAX
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                            kDifRvPlicMaxPriority));
+
+  // Set Ibex IRQ priority threshold level
+  CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
+      plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
+
+  // Enable ML core IRQs
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+}
 
 void _ottf_main(void) {
   test_status_set(kTestStatusInTest);
   init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart);
   LOG_INFO("[SMC] Start from SMC!");
+
+  // Init IRQs
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc));
+  plic_smc_configure_irqs(&plic_smc);
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+
+  // Init ML_TOP
   CHECK_DIF_OK(dif_ml_top_init(
       mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR), &ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
+                                          kDifToggleEnabled));
   dif_ml_top_reset_ctrl_en(&ml_top);
 
   // Create an array with the pointers to the image frame
@@ -85,8 +167,7 @@
   mmio_region_t ml_hps_base =
       mmio_region_from_addr(TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR +
                             TOP_MATCHA_RAM_ML_DMEM_IMG_OFFSET_ADDR);
-  mmio_region_t ml_core =
-      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
   for (int frame_idx = 0; frame_idx < kNumOfFrames; ++frame_idx) {
     // Load HPS images 0-6 into ML DMEM.
     LOG_INFO("[SMC] Start frame [%d]", frame_idx);
@@ -101,16 +182,11 @@
     }
 
     // Start up Kelvin.
-    mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET, 0x0);
-    uint32_t intr_state =
-        mmio_region_read32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET);
-    while (intr_state == 0x0) {
-      intr_state = mmio_region_read32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET);
-      busy_spin_micros(10);  // Wait for 10ms.
+    ml_top_finish_done = false;
+    CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
+    while (!ml_top_finish_done) {
+      asm volatile("wfi");
     }
-    mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET,
-                        ML_TOP_CTRL_REG_RESVAL);
-    mmio_region_write32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET, intr_state);
 
     // Verify model output
     mmio_region_t ml_model_out_base =
diff --git a/sw/device/examples/demo_hps_live/hps_demo_smc.c b/sw/device/examples/demo_hps_live/hps_demo_smc.c
index c397097..5cd11cf 100644
--- a/sw/device/examples/demo_hps_live/hps_demo_smc.c
+++ b/sw/device/examples/demo_hps_live/hps_demo_smc.c
@@ -61,6 +61,8 @@
 static volatile bool isp_frame_done = false;
 static uint32_t img_offset = TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR;
 
+static volatile bool ml_top_finish_done = false;
+
 static void handle_isp_wrapper_isp_irq() {
   sFONT *font = &Font16;
   // Set address of image buffer.
@@ -69,9 +71,8 @@
   mmio_region_write32(ml_dmem, TOP_MATCHA_RAM_ML_DMEM_CMD_OFFSET_ADDR,
                       img_offset);
   // Start up Kelvin core running the model
-  mmio_region_t ml_core =
-      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
-  mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET, 0x0);
+  ml_top_finish_done = false;
+  CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
   LOG_INFO("[SMC] Kelvin starts running model.");
 
   // Inform the host that a new frame is available.
@@ -93,14 +94,9 @@
                           font->Height, LCD_WIDTH, LCD_HEIGHT);
 
   // Wait until Kelvin finishes. Reset Kelvin core.
-  uint32_t intr_state =
-      mmio_region_read32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET);
-  while (intr_state == 0x0) {
-    intr_state = mmio_region_read32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET);
-    busy_spin_micros(delay_time_micros);
+  while (!ml_top_finish_done) {
+    asm volatile("wfi");
   }
-  mmio_region_write32(ml_core, ML_TOP_CTRL_REG_OFFSET, ML_TOP_CTRL_REG_RESVAL);
-  mmio_region_write32(ml_core, ML_TOP_INTR_STATE_REG_OFFSET, intr_state);
   LOG_INFO("[SMC] Kelvin finished running model.");
 
   // Log model score
@@ -143,6 +139,22 @@
   }
 }
 
+static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
+  switch (interrupt_id) {
+    case kTopMatchaPlicIrqIdMlTopFinish:
+      ml_top_finish_done = true;
+      break;
+    case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
+      LOG_ERROR("ML core raised fault interrupt.");
+      test_status_set(kTestStatusFailed);
+    default:
+      LOG_FATAL("ISR is not implemented!");
+      test_status_set(kTestStatusFailed);
+  }
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
 void ottf_external_isr(void) {
   // Claim the IRQ by reading the Ibex specific CC register.
   dif_rv_plic_irq_id_t interrupt_id;
@@ -156,7 +168,8 @@
   CHECK(peripheral_id == kTopMatchaPlicPeripheralIspWrapper ||
             peripheral_id == kTopMatchaPlicPeripheralCamI2c ||
             peripheral_id == kTopMatchaPlicPeripheralSpiHost2 ||
-            peripheral_id == kTopMatchaPlicPeripheralTlulMailboxSmc,
+            peripheral_id == kTopMatchaPlicPeripheralTlulMailboxSmc ||
+            peripheral_id == kTopMatchaPlicPeripheralMlTop,
         "Unexpected peripheral in ISR: %d", peripheral_id);
   switch (peripheral_id) {
     case kTopMatchaPlicPeripheralCamI2c: {
@@ -174,6 +187,10 @@
       CHECK_DIF_OK(spi_display_spi_irq_handler(&spi_display));
       break;
     }
+    case kTopMatchaPlicPeripheralMlTop: {
+      handle_ml_top_isr(interrupt_id);
+      break;
+    }
     default:
       LOG_FATAL("Peripheral is not implemented!");
   }
@@ -185,7 +202,7 @@
 }
 
 /*
- * Configures all the relevant interrupts in PLIC_SEC.
+ * Configures all the relevant interrupts in PLIC_SMC.
  */
 static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
   CHECK_DIF_OK(camera_hm01b0_irq_init(plic, kTopMatchaPlicTargetIbex0Smc));
@@ -194,24 +211,36 @@
       plic, kTopMatchaPlicIrqIdIspWrapperIsp, kDifRvPlicMaxPriority));
   CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
       plic, kTopMatchaPlicIrqIdIspWrapperMi, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                            kDifRvPlicMaxPriority));
 
   // Set Ibex IRQ priority threshold level
   CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
       plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
 
   // Enable IRQs in PLIC
+  // Enable ISP IRQs
   CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
       plic, kTopMatchaPlicIrqIdIspWrapperIsp, kTopMatchaPlicTargetIbex0Smc,
       kDifToggleEnabled));
   CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
       plic, kTopMatchaPlicIrqIdIspWrapperMi, kTopMatchaPlicTargetIbex0Smc,
       kDifToggleEnabled));
-
+  // Enable Mailbox IRQs
   CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
-      &plic_smc, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq,
+      plic, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq,
       kTopMatchaPlicTargetIbex0Smc, kDifToggleEnabled));
   CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
-      &plic_smc, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq, 1));
+      plic, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq, 1));
+  // Enable ML core IRQs
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
 }
 
 void _ottf_main(void) {
@@ -250,6 +279,10 @@
   // Init ML_TOP
   CHECK_DIF_OK(dif_ml_top_init(
       mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR), &ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
+                                          kDifToggleEnabled));
   dif_ml_top_reset_ctrl_en(&ml_top);
   LOG_INFO("[SMC] ML_TOP INIT.");
 
diff --git a/sw/device/examples/hello_world_multicore/BUILD b/sw/device/examples/hello_world_multicore/BUILD
index fe9bb58..1267682 100644
--- a/sw/device/examples/hello_world_multicore/BUILD
+++ b/sw/device/examples/hello_world_multicore/BUILD
@@ -16,7 +16,6 @@
     ],
     target_compatible_with = [OPENTITAN_CPU],
     deps = [
-        "//hw/top_matcha/ip/ml_top/data:ml_top_regs",
         "//hw/top_matcha/sw/autogen:top_matcha",
         "//sw/device/lib/dif:smc_ctrl",
         "//sw/device/lib/testing/test_framework:ottf_start",
@@ -81,9 +80,12 @@
     },
     var_name = "hello_world_multicore_smc_fpga_nexus_bin",
     deps = [
+        "//hw/top_matcha/ip/ml_top/data:ml_top_regs",
         "//hw/top_matcha/sw/autogen:top_matcha",
+        "//sw/device/lib/dif:ml_top",
         "//sw/device/lib/testing/test_framework:ottf_start_smc",
         "//sw/device/lib/testing/test_framework:test_util",
+        "@lowrisc_opentitan//sw/device/lib/runtime:irq",
         "@lowrisc_opentitan//sw/device/lib/testing/test_framework:ottf_test_config",
     ],
 )
diff --git a/sw/device/examples/hello_world_multicore/hello_world_multicore_sc.c b/sw/device/examples/hello_world_multicore/hello_world_multicore_sc.c
index 8a1248d..dc75cf9 100644
--- a/sw/device/examples/hello_world_multicore/hello_world_multicore_sc.c
+++ b/sw/device/examples/hello_world_multicore/hello_world_multicore_sc.c
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-
-#include "hw/top_matcha/ip/ml_top/data/ml_top_regs.h" // Generated.
 #include "hw/top_matcha/sw/autogen/top_matcha.h"
 #include "sw/device/examples/hello_world_multicore/hello_world_multicore_sc_loaders.h"
 #include "sw/device/lib/arch/device.h"
@@ -49,9 +47,6 @@
   // Copy binary to SMC RAM.
   load_smc();
 
-  // Load Kelvin's memory space.
-  const uint32_t kKelvinDataOffset = 0x100000;  // 1MB
-  const uint32_t kKelvinDataSize = 0x1000;      // 1024 32-bit words
   mmio_region_t ml_dmem =
       mmio_region_from_addr(TOP_MATCHA_ML_TOP_DMEM_BASE_ADDR);
 
@@ -64,43 +59,10 @@
   // Copy binary to ML_DMEM.
   load_kelvin();
 
-  // Fill Kelvin's working region with known values.
-  for (uint32_t i = kKelvinDataOffset; i < kKelvinDataOffset + kKelvinDataSize;
-       i += sizeof(uint32_t)) {
-    mmio_region_write32(ml_dmem, i, i);
-  }
-
   // Enable SMC.
   CHECK_DIF_OK(dif_smc_ctrl_init(
       mmio_region_from_addr(TOP_MATCHA_SMC_CTRL_BASE_ADDR), &smc_ctrl));
   CHECK_DIF_OK(dif_smc_ctrl_set_en(&smc_ctrl));
 
-  // Start up Kelvin.
-  mmio_region_t base_addr =
-      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
-  mmio_region_write32(base_addr, ML_TOP_CTRL_REG_OFFSET,
-                      ML_TOP_CTRL_REG_RESVAL);
-  mmio_region_write32(base_addr, ML_TOP_CTRL_REG_OFFSET, 0x0);
-  uint32_t intr_state =
-      mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
-  while (intr_state == 0x0) {
-    intr_state = mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
-    busy_spin_micros(10 * 1000); // Wait for 10ms.
-  }
-
-  LOG_INFO("Kelvin finished executing.");
-  // Received interrupts from Kelvin core, check if only FINISH asserted
-  CHECK(intr_state == (1 << ML_TOP_INTR_STATE_FINISH_BIT),
-        "INTR_STATE read out: expected : 0x%x | actual: 0x%x",
-        (1 << ML_TOP_INTR_STATE_FINISH_BIT), intr_state);
-
-  for (uint32_t i = kKelvinDataOffset; i < kKelvinDataOffset + kKelvinDataSize;
-       i += sizeof(uint32_t)) {
-    uint32_t val = mmio_region_read32(ml_dmem, i);
-    CHECK(val == i + 1,
-          "Mismatch data at offset 0x%h - Expected: 0x%h | Actual: 0x%h", i,
-          i + 1, val);
-  }
-  test_status_set(kTestStatusPassed);
   asm volatile("wfi");
 }
diff --git a/sw/device/examples/hello_world_multicore/hello_world_multicore_smc.c b/sw/device/examples/hello_world_multicore/hello_world_multicore_smc.c
index 24d382b..4f0fe88 100644
--- a/sw/device/examples/hello_world_multicore/hello_world_multicore_smc.c
+++ b/sw/device/examples/hello_world_multicore/hello_world_multicore_smc.c
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
+#include "hw/top_matcha/ip/ml_top/data/ml_top_regs.h"  // Generated.
 #include "hw/top_matcha/sw/autogen/top_matcha.h"
 #include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_ml_top.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
 #include "sw/device/lib/dif/dif_uart.h"
+#include "sw/device/lib/runtime/irq.h"
 #include "sw/device/lib/runtime/print.h"
 #include "sw/device/lib/testing/test_framework/check.h"
 #include "sw/device/lib/testing/test_framework/ottf_test_config.h"
@@ -24,10 +28,115 @@
 
 OTTF_DEFINE_TEST_CONFIG();
 
+static dif_ml_top_t ml_top;
+static dif_rv_plic_t plic_smc;
 static dif_uart_t smc_uart;
 
+static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
+  switch (interrupt_id) {
+    case kTopMatchaPlicIrqIdMlTopFinish:
+      break;
+    case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
+      LOG_ERROR("ML core raised fault interrupt.");
+      test_status_set(kTestStatusFailed);
+    default:
+      LOG_FATAL("ISR is not implemented!");
+      test_status_set(kTestStatusFailed);
+  }
+
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
+void ottf_external_isr(void) {
+  // Claim the IRQ by reading the Ibex specific CC register.
+  dif_rv_plic_irq_id_t interrupt_id;
+
+  CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                     &interrupt_id));
+
+  // Check if the interrupted peripheral is ISP WRAPPER.
+  top_matcha_plic_peripheral_smc_t peripheral_id =
+      top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id];
+  CHECK(peripheral_id == kTopMatchaPlicPeripheralMlTop,
+        "Unexpected peripheral in ISR: %d", peripheral_id);
+  switch (peripheral_id) {
+    case kTopMatchaPlicPeripheralMlTop: {
+      handle_ml_top_isr(interrupt_id);
+      break;
+    }
+    default:
+      LOG_FATAL("Peripheral is not implemented!");
+  }
+
+  // Complete the IRQ by writing the IRQ source to the Ibex specific CC
+  // register.
+  CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                        interrupt_id));
+}
+
+// Configures all relevant interrupts in PLIC_SMC.
+static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
+  // Set IRQ priorities to MAX
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                            kDifRvPlicMaxPriority));
+
+  // Set Ibex IRQ priority threshold level
+  CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
+      plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
+
+  // Enable ML core IRQs
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+}
+
 void _ottf_main(void) {
   init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart);
   LOG_INFO("hello_world_multicore_smc");
-  asm volatile("wfi");
+
+  // Init IRQs
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc));
+  plic_smc_configure_irqs(&plic_smc);
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+
+  // Init ML_TOP
+  CHECK_DIF_OK(dif_ml_top_init(
+      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR), &ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+
+  // Load Kelvin's memory space.
+  const uint32_t kKelvinDataOffset = 0x100000;  // 1MB
+  const uint32_t kKelvinDataSize = 0x1000;      // 1024 32-bit words
+  mmio_region_t ml_dmem =
+      mmio_region_from_addr(TOP_MATCHA_ML_TOP_DMEM_BASE_ADDR);
+
+  // Fill Kelvin's working region with known values.
+  for (uint32_t i = kKelvinDataOffset; i < kKelvinDataOffset + kKelvinDataSize;
+       i += sizeof(uint32_t)) {
+    mmio_region_write32(ml_dmem, i, i);
+  }
+
+  CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
+
+  LOG_INFO("Kelvin finished executing.");
+  for (uint32_t i = kKelvinDataOffset; i < kKelvinDataOffset + kKelvinDataSize;
+       i += sizeof(uint32_t)) {
+    uint32_t val = mmio_region_read32(ml_dmem, i);
+    CHECK(val == i + 1,
+          "Mismatch data at offset 0x%h - Expected: 0x%h | Actual: 0x%h", i,
+          i + 1, val);
+  }
+  test_status_set(kTestStatusPassed);
 }
diff --git a/sw/device/lib/dif/dif_ml_top.c b/sw/device/lib/dif/dif_ml_top.c
index 8be195e..f2ca845 100644
--- a/sw/device/lib/dif/dif_ml_top.c
+++ b/sw/device/lib/dif/dif_ml_top.c
@@ -36,6 +36,15 @@
   return kDifOk;
 }
 
+dif_result_t dif_ml_top_release_ctrl_en(const dif_ml_top_t *ml_top) {
+  if (ml_top == NULL) {
+    return kDifBadArg;
+  }
+
+  mmio_region_write32(ml_top->base_addr, ML_TOP_CTRL_REG_OFFSET, 0x0);
+  return kDifOk;
+}
+
 dif_result_t dif_ml_top_read_ctrl_en(const dif_ml_top_t *ml_top, uint32_t *result) {
   if (ml_top == NULL) {
     return kDifBadArg;
diff --git a/sw/device/lib/dif/dif_ml_top.h b/sw/device/lib/dif/dif_ml_top.h
index af29c7a..81f079c 100644
--- a/sw/device/lib/dif/dif_ml_top.h
+++ b/sw/device/lib/dif/dif_ml_top.h
@@ -15,9 +15,8 @@
  * limitations under the License.
  */
 
-
-#ifndef MATCHA_SW_DEVICE_LIB_DIF_DIF_ML_TOP_H_
-#define MATCHA_SW_DEVICE_LIB_DIF_DIF_ML_TOP_H_
+#ifndef SW_DEVICE_LIB_DIF_DIF_ML_TOP_H_
+#define SW_DEVICE_LIB_DIF_DIF_ML_TOP_H_
 
 /**
  * @file
@@ -34,11 +33,14 @@
 
 // ML_Top Control Register
 dif_result_t dif_ml_top_reset_ctrl_en(const dif_ml_top_t *ml_top);
-dif_result_t dif_ml_top_read_ctrl_en(const dif_ml_top_t *ml_top, uint32_t *result);
+dif_result_t dif_ml_top_release_ctrl_en(const dif_ml_top_t *ml_top);
+dif_result_t dif_ml_top_read_ctrl_en(const dif_ml_top_t *ml_top,
+                                     uint32_t *result);
 
 // ML_Top Interrupt Enable Register
 dif_result_t dif_ml_top_reset_intr_en(const dif_ml_top_t *ml_top);
-dif_result_t dif_ml_top_read_intr_en(const dif_ml_top_t *ml_top, uint32_t *result);
+dif_result_t dif_ml_top_read_intr_en(const dif_ml_top_t *ml_top,
+                                     uint32_t *result);
 
 /**
  * Below functions are auto-generated. Keep them here for now for reference.
@@ -140,4 +142,4 @@
 }  // extern "C"
 #endif  // __cplusplus
 
-#endif  // MATCHA_SW_DEVICE_LIB_DIF_DIF_ML_TOP_H_
+#endif  // SW_DEVICE_LIB_DIF_DIF_ML_TOP_H_
diff --git a/sw/device/tests/smc/BUILD b/sw/device/tests/smc/BUILD
index deee857..a86fe8d 100644
--- a/sw/device/tests/smc/BUILD
+++ b/sw/device/tests/smc/BUILD
@@ -278,6 +278,8 @@
     },
     deps = [
         "//hw/top_matcha/ip/ml_top/data:ml_top_regs",
+        "//sw/device/lib/dif:ml_top",
+        "//sw/device/lib/dif:rv_plic_smc",
         "//sw/device/tests:test_lib_smc",
     ],
 )
@@ -298,6 +300,8 @@
     },
     deps = [
         "//hw/top_matcha/ip/ml_top/data:ml_top_regs",
+        "//sw/device/lib/dif:ml_top",
+        "//sw/device/lib/dif:rv_plic_smc",
         "//sw/device/tests:test_lib_smc",
     ],
 )
diff --git a/sw/device/tests/smc/smc_kelvin_checksum_test.c b/sw/device/tests/smc/smc_kelvin_checksum_test.c
index 85a4e48..5a81c20 100644
--- a/sw/device/tests/smc/smc_kelvin_checksum_test.c
+++ b/sw/device/tests/smc/smc_kelvin_checksum_test.c
@@ -20,8 +20,11 @@
 #include "hw/top_matcha/ip/ml_top/data/ml_top_regs.h"  // Generated.
 #include "hw/top_matcha/sw/autogen/top_matcha.h"
 #include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_ml_top.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
 #include "sw/device/lib/dif/dif_uart.h"
 #include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/irq.h"
 #include "sw/device/lib/runtime/log.h"
 #include "sw/device/lib/runtime/print.h"
 #include "sw/device/lib/testing/test_framework/check.h"
@@ -35,18 +38,99 @@
 
 OTTF_DEFINE_TEST_CONFIG();
 
+static dif_ml_top_t ml_top;
+static dif_rv_plic_t plic_smc;
 static dif_uart_t smc_uart;
 
-void _ottf_main(void) {
-  uint32_t mem_val;
-  uint32_t intr_state;
+static volatile bool ml_top_finish_done = false;
 
+static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
+  switch (interrupt_id) {
+    case kTopMatchaPlicIrqIdMlTopFinish:
+      ml_top_finish_done = true;
+      break;
+    case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
+      LOG_ERROR("ML core raised fault interrupt.");
+      test_status_set(kTestStatusFailed);
+    default:
+      LOG_FATAL("ISR is not implemented!");
+      test_status_set(kTestStatusFailed);
+  }
+
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
+void ottf_external_isr(void) {
+  // Claim the IRQ by reading the Ibex specific CC register.
+  dif_rv_plic_irq_id_t interrupt_id;
+
+  CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                     &interrupt_id));
+
+  // Check if the interrupted peripheral is ISP WRAPPER.
+  top_matcha_plic_peripheral_smc_t peripheral_id =
+      top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id];
+  CHECK(peripheral_id == kTopMatchaPlicPeripheralMlTop,
+        "Unexpected peripheral in ISR: %d", peripheral_id);
+  switch (peripheral_id) {
+    case kTopMatchaPlicPeripheralMlTop: {
+      handle_ml_top_isr(interrupt_id);
+      break;
+    }
+    default:
+      LOG_FATAL("Peripheral is not implemented!");
+  }
+
+  // Complete the IRQ by writing the IRQ source to the Ibex specific CC
+  // register.
+  CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                        interrupt_id));
+}
+
+// Configures all relevant interrupts in PLIC_SMC.
+static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
+  // Set IRQ priorities to MAX
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                            kDifRvPlicMaxPriority));
+
+  // Set Ibex IRQ priority threshold level
+  CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
+      plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
+
+  // Enable ML core IRQs
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+}
+
+void _ottf_main(void) {
   test_status_set(kTestStatusInTest);
   // Initialize the SMC UART to enable logging for non-DV simulation platforms.
   if (kDeviceType != kDeviceSimDV) {
     init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart);
   }
 
+  // Init IRQs
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc));
+  plic_smc_configure_irqs(&plic_smc);
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+
+  // Init ML_TOP
+  CHECK_DIF_OK(dif_ml_top_init(
+      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR), &ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
+                                          kDifToggleEnabled));
+
   // Write DMEM with initial values
   const uint32_t *input = (const uint32_t *)hps_0;
   mmio_region_t ml_dmem_base = mmio_region_from_addr(
@@ -61,32 +145,21 @@
   mmio_region_t base_addr =
       mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
   // Un-freeze clock and Reset of ML_TOP
-  mmio_region_write32(base_addr, ML_TOP_CTRL_REG_OFFSET,
-                      ML_TOP_CTRL_REG_RESVAL);
-  mem_val = mmio_region_read32(base_addr, ML_TOP_CTRL_REG_OFFSET);
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  uint32_t mem_val = mmio_region_read32(base_addr, ML_TOP_CTRL_REG_OFFSET);
   CHECK(mem_val == ML_TOP_CTRL_REG_RESVAL,
         "ML_TOP_CTRL read out: expected : 0x%x | actual: 0x%x",
         ML_TOP_CTRL_REG_RESVAL, mem_val);
 
   // Release the Reset of ML_TOP
-  mmio_region_write32(base_addr, ML_TOP_CTRL_REG_OFFSET, 0x0);
+  ml_top_finish_done = false;
+  CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
 
-  // Wait for a interrupt
-  intr_state = mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
-  CHECK(intr_state == 0,
-        "ML_TOP_Core offset 0 INTR_STATE - Expected: 0x0 | Actual: 0x%x",
-        intr_state);
-
-  while (intr_state == 0x0) {
-    intr_state = mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
-    busy_spin_micros(200);
+  // Wait until Kelvin finishes.
+  while (!ml_top_finish_done) {
+    asm volatile("wfi");
   }
 
-  // Received interrupts from Kelvin core, check if only FINISH asserted
-  CHECK(intr_state == (1 << ML_TOP_INTR_STATE_FINISH_BIT),
-        "INTR_STATE read out: expected : 0x%x | actual: 0x%x",
-        (1 << ML_TOP_INTR_STATE_FINISH_BIT), intr_state);
-
   // Check kelvin result. The program sums to the data stored in the test
   // range 0x5A300000 to 0x5A312c00, and stored at 0x5A380000
   ml_dmem_base = mmio_region_from_addr(TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR);
diff --git a/sw/device/tests/smc/smc_kelvin_hello_test.c b/sw/device/tests/smc/smc_kelvin_hello_test.c
index fab6c61..8a6babf 100644
--- a/sw/device/tests/smc/smc_kelvin_hello_test.c
+++ b/sw/device/tests/smc/smc_kelvin_hello_test.c
@@ -19,8 +19,11 @@
 #include "hw/top_matcha/ip/ml_top/data/ml_top_regs.h"  // Generated.
 #include "hw/top_matcha/sw/autogen/top_matcha.h"
 #include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_ml_top.h"
+#include "sw/device/lib/dif/dif_rv_plic.h"
 #include "sw/device/lib/dif/dif_uart.h"
 #include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/irq.h"
 #include "sw/device/lib/runtime/log.h"
 #include "sw/device/lib/runtime/print.h"
 #include "sw/device/lib/testing/test_framework/check.h"
@@ -30,8 +33,77 @@
 
 OTTF_DEFINE_TEST_CONFIG();
 
+static dif_ml_top_t ml_top;
+static dif_rv_plic_t plic_smc;
 static dif_uart_t smc_uart;
 
+static volatile bool ml_top_finish_done = false;
+
+static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
+  switch (interrupt_id) {
+    case kTopMatchaPlicIrqIdMlTopFinish:
+      ml_top_finish_done = true;
+      break;
+    case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
+      LOG_ERROR("ML core raised fault interrupt.");
+      test_status_set(kTestStatusFailed);
+    default:
+      LOG_FATAL("ISR is not implemented!");
+      test_status_set(kTestStatusFailed);
+  }
+
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
+}
+
+void ottf_external_isr(void) {
+  // Claim the IRQ by reading the Ibex specific CC register.
+  dif_rv_plic_irq_id_t interrupt_id;
+
+  CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                     &interrupt_id));
+
+  // Check if the interrupted peripheral is ISP WRAPPER.
+  top_matcha_plic_peripheral_smc_t peripheral_id =
+      top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id];
+  CHECK(peripheral_id == kTopMatchaPlicPeripheralMlTop,
+        "Unexpected peripheral in ISR: %d", peripheral_id);
+  switch (peripheral_id) {
+    case kTopMatchaPlicPeripheralMlTop: {
+      handle_ml_top_isr(interrupt_id);
+      break;
+    }
+    default:
+      LOG_FATAL("Peripheral is not implemented!");
+  }
+
+  // Complete the IRQ by writing the IRQ source to the Ibex specific CC
+  // register.
+  CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
+                                        interrupt_id));
+}
+
+// Configures all relevant interrupts in PLIC_SMC.
+static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
+  // Set IRQ priorities to MAX
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
+      plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                            kDifRvPlicMaxPriority));
+
+  // Set Ibex IRQ priority threshold level
+  CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
+      plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
+
+  // Enable ML core IRQs
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+  CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
+                                           kTopMatchaPlicTargetIbex0Smc,
+                                           kDifToggleEnabled));
+}
+
 void _ottf_main(void) {
   uint32_t mem_val;
   uint32_t intr_state;
@@ -44,12 +116,26 @@
     init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart);
   }
 
+  // Init IRQs
+  CHECK_DIF_OK(dif_rv_plic_init(
+      mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc));
+  plic_smc_configure_irqs(&plic_smc);
+  irq_global_ctrl(true);
+  irq_external_ctrl(true);
+
+  // Init ML_TOP
+  mmio_region_t base_addr =
+      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
+  CHECK_DIF_OK(dif_ml_top_init(base_addr, &ml_top));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
+                                          kDifToggleEnabled));
+  CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
+                                          kDifToggleEnabled));
+
   LOG_INFO("Hello Shodan! let's do a Kelvin core simple test!");
 
   // Start testing ML_TOP_Core
   test_status_set(kTestStatusInTest);
-  mmio_region_t base_addr =
-      mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR);
 
   intr_state = mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
   CHECK(intr_state == 0,
@@ -64,7 +150,8 @@
         intr_state);
 
   mem_val = mmio_region_read32(base_addr, ML_TOP_INTR_ENABLE_REG_OFFSET);
-  CHECK(mem_val == 0,
+  CHECK(mem_val == (1 << ML_TOP_INTR_COMMON_FINISH_BIT |
+                    1 << ML_TOP_INTR_COMMON_FAULT_BIT),
         "ML_TOP_TOP offset 4 INTR_ENABLE - Expected: 0 | Actual: 0x%x",
         mem_val);
   mem_val = mmio_region_read32(base_addr, ML_TOP_INTR_TEST_REG_OFFSET);
@@ -85,35 +172,24 @@
   }
 
   // Un-freeze clock and Reset of ML_TOP
-  mmio_region_write32(base_addr, ML_TOP_CTRL_REG_OFFSET,
-                      ML_TOP_CTRL_REG_RESVAL);
+  CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
   mem_val = mmio_region_read32(base_addr, ML_TOP_CTRL_REG_OFFSET);
   CHECK(mem_val == ML_TOP_CTRL_REG_RESVAL,
         "ML_TOP_CTRL read out: expected : 0x%x | actual: 0x%x",
         ML_TOP_CTRL_REG_RESVAL, mem_val);
 
   // Release the Reset of ML_TOP
-  mmio_region_write32(base_addr, ML_TOP_CTRL_REG_OFFSET, 0x0);
+  ml_top_finish_done = false;
+  CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
   mem_val = mmio_region_read32(base_addr, ML_TOP_CTRL_REG_OFFSET);
   CHECK(mem_val == 0x0, "ML_TOP_CTRL read out: expected : 0x0 | actual: 0x%x",
         mem_val);
 
-  // Wait for a interrupt
-  intr_state = mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
-  CHECK(intr_state == 0,
-        "ML_TOP_Core offset 0 INTR_STATE - Expected: 0x0 | Actual: 0x%x",
-        intr_state);
-
-  while (intr_state == 0x0) {
-    intr_state = mmio_region_read32(base_addr, ML_TOP_INTR_STATE_REG_OFFSET);
-    busy_spin_micros(200);
+  // Wait until Kelvin finishes.
+  while (!ml_top_finish_done) {
+    asm volatile("wfi");
   }
 
-  // Received interrupts from Kelvin core, check if only FINISH asserted
-  CHECK(intr_state == (1 << ML_TOP_INTR_STATE_FINISH_BIT),
-        "INTR_STATE read out: expected : 0x%x | actual: 0x%x",
-        (1 << ML_TOP_INTR_STATE_FINISH_BIT), intr_state);
-
   // Check Kelvin result. The program adds 1 to the data stored in the test
   // range 0x5A100000 to 0x5A101000.
   for (uint32_t i = kKelvinDataOffset; i < kKelvinDataOffset + kKelvinDataSize;