Fix problem with chip_sw_sram_ctrl_scrambled_access test

Bug: 305068600

After the scramble test of retention mem, use sw reset to clear
the alerts. Stop the test right after boot up.

Change-Id: I66e01a3357074ebc11470c27c2e14103a07fa796
diff --git a/sw/device/tests/sim_dv/BUILD b/sw/device/tests/sim_dv/BUILD
index b50b41d..c1b3c62 100644
--- a/sw/device/tests/sim_dv/BUILD
+++ b/sw/device/tests/sim_dv/BUILD
@@ -396,6 +396,23 @@
     ],
 )
 
+matcha_dv_test(
+    name = "sram_ctrl_scrambled_access_test",
+    srcs = ["sram_ctrl_scrambled_access_test.c"],
+    deps = [
+        "//sw/device/lib/dif:rstmgr",
+        "//sw/device/tests:test_dv_lib",
+        "@lowrisc_opentitan//hw/ip/sram_ctrl/data:sram_ctrl_regs",
+        "@lowrisc_opentitan//sw/device/lib/base:macros",
+        "@lowrisc_opentitan//sw/device/lib/base:multibits",
+        "@lowrisc_opentitan//sw/device/lib/base:stdasm",
+        "@lowrisc_opentitan//sw/device/lib/dif:flash_ctrl",
+        "@lowrisc_opentitan//sw/device/lib/testing:flash_ctrl_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:rstmgr_testutils",
+        "@lowrisc_opentitan//sw/device/lib/testing:sram_ctrl_testutils",
+    ],
+)
+
 ################################################################################
 #  Place the Opentitan-sourced DV test below.                                  #
 #  Opentitan DV binary build flow does not support centOS7, so we pull the     #
@@ -550,21 +567,6 @@
 )
 
 matcha_dv_test(
-    name = "sram_ctrl_scrambled_access_test",
-    srcs = ["@lowrisc_opentitan//sw/device/tests/sim_dv:sram_ctrl_scrambled_access_test.c"],
-    deps = [
-        "//sw/device/lib/dif:rstmgr",
-        "//sw/device/tests:test_dv_lib_opentitan",
-        "@lowrisc_opentitan//hw/ip/sram_ctrl/data:sram_ctrl_regs",
-        "@lowrisc_opentitan//sw/device/lib/base:macros",
-        "@lowrisc_opentitan//sw/device/lib/base:multibits",
-        "@lowrisc_opentitan//sw/device/lib/base:stdasm",
-        "@lowrisc_opentitan//sw/device/lib/testing:rstmgr_testutils",
-        "@lowrisc_opentitan//sw/device/lib/testing:sram_ctrl_testutils",
-    ],
-)
-
-matcha_dv_test(
     name = "lc_ctrl_program_error",
     srcs = ["@lowrisc_opentitan//sw/device/tests/sim_dv:lc_ctrl_program_error.c"],
     deps = [
diff --git a/sw/device/tests/sim_dv/sram_ctrl_scrambled_access_test.c b/sw/device/tests/sim_dv/sram_ctrl_scrambled_access_test.c
new file mode 100644
index 0000000..4f6442a
--- /dev/null
+++ b/sw/device/tests/sim_dv/sram_ctrl_scrambled_access_test.c
@@ -0,0 +1,434 @@
+// Copyright 2023 Google LLC.
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "hw/top_matcha/sw/autogen/top_matcha.h"
+#include "rstmgr_regs.h"     // Generated.
+#include "sram_ctrl_regs.h"  // Generated.
+#include "sw/device/lib/base/macros.h"
+#include "sw/device/lib/base/mmio.h"
+#include "sw/device/lib/base/multibits.h"
+#include "sw/device/lib/base/stdasm.h"
+#include "sw/device/lib/dif/dif_flash_ctrl.h"
+#include "sw/device/lib/dif/dif_rstmgr.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/flash_ctrl_testutils.h"
+#include "sw/device/lib/testing/rand_testutils.h"
+#include "sw/device/lib/testing/rstmgr_testutils.h"
+#include "sw/device/lib/testing/sram_ctrl_testutils.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_main.h"
+
+OTTF_DEFINE_TEST_CONFIG();
+
+enum {
+  /**
+   * Retention SRAM start address (inclusive).
+   */
+  kRetSramBaseAddr = TOP_MATCHA_SRAM_CTRL_RET_AON_RAM_BASE_ADDR,
+
+  // See `sw/device/silicon_creator/lib/drivers/retention_sram.h`.
+  kRetSramOwnerAddr = kRetSramBaseAddr + 4 + 2048,
+  kRetRamLastAddr =
+      kRetSramBaseAddr + TOP_MATCHA_SRAM_CTRL_RET_AON_RAM_SIZE_BYTES - 1,
+
+  kTestBufferSizeWords = 16,
+  kTestBufferSizeBytes = kTestBufferSizeWords * sizeof(uint32_t),
+
+  /**
+   * Note that there are `2^32` valid code words and that each non-valid code
+   * word triggers an error. Therefore, the probability that a random 39-bit
+   * word triggers an error is: `(2^39 - 2^32)/ 2^39 = 127/128`. Then the
+   * probability that all `kPatternTestWords` triggers an errors is
+   * `(127/128)^kPatternTestWords` after re-scrambling.
+   *
+   * The Generic formula:
+   *
+   *               (w-i)
+   *             127
+   * Pr(i) =  -------- x (w choose i)
+   *                w
+   *             128
+   * Where:
+   *      w = The number of words tested.
+   *      i = The number of words that may not generate errors.
+   *      Pr(i) = Probability that i words will not generate an ECC error.
+   *
+   * So for i in (0..3):
+   *
+   * ``` Python
+   * from math import comb
+   * w = 32
+   * t = 0
+   * for i in range(4):
+   *    p = ((127**(w-i))/(128**w)) * comb(w,i)
+   *    t += p
+   *    print(f'Pr({i}): { round(p, 4)},\tsum{{Pr(0-{i})}}: {round(t, 6)}')
+   * ```
+   * ```
+   * Pr(0): 0.778,   sum{Pr(0-0)}: 0.778037
+   * Pr(1): 0.196,   sum{Pr(0-1)}: 0.974077
+   * Pr(2): 0.0239,  sum{Pr(0-2)}: 0.998004
+   * Pr(3): 0.0019,  sum{Pr(0-3)}: 0.999888
+   * ```
+   * So by choosing 3 as the floor limit we will a have probability of `1 -
+   * 0.998004 = 0.1996%` that this test would fail randomly due to ECC errors
+   * not being generated.
+   *
+   * Note: Although `kTestBufferSizeWords` is 16 we use 32 to compute the
+   * probability since we perform two tests here RET SRAM and main SRAM.
+   */
+
+  kEccErrorsFalsePositiveFloorLimit = 3,
+};
+
+static_assert(kTestBufferSizeWords == 16,
+              "kBackdoorTestWords changed, so "
+              "kEccErrorsFalsePositiveFloorLimit should be "
+              "computed again");
+
+typedef struct {
+  uint32_t backdoor[kTestBufferSizeWords];
+  uint32_t pattern[kTestBufferSizeWords];
+  uint32_t ecc_error_counter;
+} scramble_test_frame;
+
+static scramble_test_frame *scrambling_frame;
+static scramble_test_frame *reference_frame;
+
+static dif_flash_ctrl_state_t flash_ctrl;
+static dif_sram_ctrl_t ret_sram;
+static dif_rstmgr_t rstmgr;
+
+/**
+ * Test pattern to be written and read from SRAM.
+ */
+static const uint32_t kRamTestPattern1[kTestBufferSizeWords] = {
+    0xA5A5A5A5, 0xA23DE94C, 0xD82A4FB0, 0xE3CA4D62, 0xA5A5A5A5, 0xA23DE94C,
+    0xD82A4FB0, 0xE3CA4D62, 0xA5A5A5A5, 0xA23DE94C, 0xD82A4FB0, 0xE3CA4D62,
+    0xA5A5A5A5, 0xA23DE94C, 0xD82A4FB0, 0xE3CA4D62,
+};
+
+/**
+ * Test pattern to be written and read from SRAM.
+ */
+static const uint32_t kRamTestPattern2[kTestBufferSizeWords] = {
+    0x5A5A5A5A, 0x3CFB4A77, 0x304C6528, 0xFAEFD5CC, 0x5A5A5A5A, 0x3CFB4A77,
+    0x304C6528, 0xFAEFD5CC, 0x5A5A5A5A, 0x3CFB4A77, 0x304C6528, 0xFAEFD5CC,
+    0x5A5A5A5A, 0x3CFB4A77, 0x304C6528, 0xFAEFD5CC,
+};
+
+/**
+ * Expected data for the backdoor write test, to be written from the testbench.
+ */
+static const uint8_t kBackdoorExpectedBytes[kTestBufferSizeBytes];
+
+/**
+ * Performs scrambling, saves the test relevant data and resets the system.
+ *
+ * This code is written in assembly because MAIN SRAM addresses will be
+ * scrambled, which has a similar effect to overwriting with pseudo-random
+ * data. This will thrash the SRAM (including .bss, .data segments and the
+ * stack), effectively rendering the C runtime environment invalid.
+ *
+ * This function saves contents of the `scrambling_frame` struct in the main
+ * SRAM including the data written from the testbench to the RETENTION SRAM,
+ * which is kept intact across the system reboot.
+ */
+static noreturn void main_sram_scramble(void) {
+  asm volatile(
+      // Save the tests frames addresses before the scrambling.
+      "lw a2, 0(%[mainFrame])                                        \n"
+      "lw a3, 0(%[retFrame])                                         \n"
+      // Request the new scrambling key for MAIN SRAM.
+      "li t0, 0x1                                                    \n"
+      "sw t0, %[kSramCtrlOffset](%[kSramCtrlRegsBase])               \n"
+
+      // Busy loop - waiting for scrambling to finish.
+      ".L_scrambling_busy_loop:                                      \n"
+      "  lw t0, %[kSramCtrlStatusOffset](%[kSramCtrlRegsBase])       \n"
+      "  andi t0, t0, %[kSramCtrlKeyScrDone]                         \n"
+      "  beqz t0, .L_scrambling_busy_loop                            \n"
+
+      // Restore the tests frames addresses after the scrambling .
+      "sw a2, 0(%[mainFrame])                                        \n"
+      "sw a3, 0(%[retFrame])                                         \n"
+
+      // Copy the backdoor and pattern buffers from main to the retention SRAM.
+      " addi t1, a3,  %[kCopyLen]                                   \n"
+      ".L_buffer_copy_loop:                                         \n"
+      "  lw t0, 0(a2)                                               \n"
+      "  sw t0, 0(a3)                                               \n"
+      "  addi a2, a2, 4                                             \n"
+      "  addi a3, a3, 4                                             \n"
+      "  blt a3, t1, .L_buffer_copy_loop                            \n"
+
+      // Trigger the software system reset via the Reset Manager.
+      "li t0, %[kMultiBitTrue]                                      \n"
+      "sw t0, %[kRstMgrResetReq](%[kRstMgrRegsBase])                \n"
+
+      // Satisfy the `noreturn` promise to the compiler.
+      ".L_infinite_loop:                                            \n"
+      "  wfi                                                        \n"
+      "  j .L_infinite_loop"
+      : /* No outputs. */
+      : [kMultiBitTrue] "I"(kMultiBitBool4True),
+
+        [kSramCtrlRegsBase] "r"(TOP_MATCHA_SRAM_CTRL_MAIN_REGS_BASE_ADDR),
+        [kSramCtrlOffset] "I"(SRAM_CTRL_CTRL_REG_OFFSET),
+        [kSramCtrlStatusOffset] "I"(SRAM_CTRL_STATUS_REG_OFFSET),
+
+        [kSramCtrlKeyScrDone] "I"(0x1 << SRAM_CTRL_STATUS_SCR_KEY_VALID_BIT),
+
+        [mainFrame] "r"(&scrambling_frame), [retFrame] "r"(&reference_frame),
+        [kCopyLen] "I"(sizeof(reference_frame->pattern) +
+                       sizeof(reference_frame->backdoor)),
+
+        [kRstMgrRegsBase] "r"(TOP_MATCHA_RSTMGR_AON_BASE_ADDR),
+        [kRstMgrResetReq] "I"(RSTMGR_RESET_REQ_REG_OFFSET)
+      : "t0", "t1", "a2", "a3");
+
+  OT_UNREACHABLE();
+}
+
+/**
+ * Prepares the buffers.
+ *
+ * Makes sure that both buffers can be read and written to, and are initialized
+ * to the opposite patterns.
+ */
+static void prepare_sram_for_scrambling(void) {
+  LOG_INFO("Writing to addr 0x%x", scrambling_frame->pattern);
+  // Make sure we can write and read the buffer in SRAM under test.
+  sram_ctrl_testutils_write(
+      (uint32_t)scrambling_frame->pattern,
+      (sram_ctrl_testutils_data_t){.words = kRamTestPattern2,
+                                   .len = kTestBufferSizeWords});
+  sram_ctrl_testutils_write(
+      (uint32_t)scrambling_frame->pattern,
+      (sram_ctrl_testutils_data_t){.words = kRamTestPattern1,
+                                   .len = kTestBufferSizeWords});
+
+  LOG_INFO("Checking addr 0x%x", scrambling_frame->pattern);
+  CHECK_ARRAYS_EQ(scrambling_frame->pattern, kRamTestPattern1,
+                  kTestBufferSizeWords);
+
+  LOG_INFO("Writing to addr 0x%x", reference_frame->pattern);
+  // Make sure we can write and read to the reference SRAM.
+  sram_ctrl_testutils_write(
+      (uint32_t)reference_frame->pattern,
+      (sram_ctrl_testutils_data_t){.words = kRamTestPattern1,
+                                   .len = kTestBufferSizeWords});
+  sram_ctrl_testutils_write(
+      (uint32_t)reference_frame->pattern,
+      (sram_ctrl_testutils_data_t){.words = kRamTestPattern2,
+                                   .len = kTestBufferSizeWords});
+  LOG_INFO("Checking addr 0x%x", reference_frame);
+  CHECK_ARRAYS_EQ(reference_frame->pattern, kRamTestPattern2,
+                  kTestBufferSizeWords);
+}
+
+static void execute_main_sram_test() {
+  LOG_INFO("ut_backdoor: %x,ut_patternt: %x,ut_ecc_error_counter: %x",
+           scrambling_frame->backdoor, scrambling_frame->pattern,
+           &scrambling_frame->ecc_error_counter);
+  LOG_INFO("ref_backdoor: %x,ref_patternt: %x,ref_ecc_error_counter: %x",
+           reference_frame->backdoor, reference_frame->pattern,
+           &reference_frame->ecc_error_counter);
+  // Reset the Ecc error count.
+  reference_frame->ecc_error_counter = 0;
+
+  LOG_INFO("Preparing test...");
+  prepare_sram_for_scrambling();
+  LOG_INFO("Scrambling...");
+  main_sram_scramble();
+}
+
+static void check_sram_data(scramble_test_frame *mem_frame) {
+  LOG_INFO("Checking addr 0x%x", mem_frame->pattern);
+  uint32_t tmp_buffer[kTestBufferSizeWords];
+  memcpy(tmp_buffer, (const uint8_t *)mem_frame->pattern, sizeof(tmp_buffer));
+
+  CHECK_ARRAYS_NE((uint32_t *)tmp_buffer, kRamTestPattern1,
+                  kTestBufferSizeWords);
+  CHECK_ARRAYS_NE((uint32_t *)tmp_buffer, kRamTestPattern2,
+                  kTestBufferSizeWords);
+
+  LOG_INFO("Checking Ecc %d", reference_frame->ecc_error_counter);
+  CHECK(reference_frame->ecc_error_counter <= kTestBufferSizeWords);
+  // Statistically there is always a chance that after changing the scrambling
+  // key the ECC bits are correct and no IRQ is triggered. So we tolerate a
+  // minimum of false positives.
+  // Note: `false_positives` should be incremented across the tests, so we make
+  // it `static`.
+  static int32_t false_positives = 0;
+  false_positives += kTestBufferSizeWords - reference_frame->ecc_error_counter;
+
+  if (false_positives > 0) {
+    CHECK(false_positives <= kEccErrorsFalsePositiveFloorLimit,
+          "Failed as it didn't generate enough ECC errors(%d/%d)",
+          false_positives, kEccErrorsFalsePositiveFloorLimit);
+
+    LOG_INFO("Passing with a remark, %d words didn't generate ECC errors",
+             false_positives);
+  }
+
+  // Reading before comparing just to make sure it will always read all the
+  // words and the right amount of ECC errors will be generated.
+  LOG_INFO("Checking backdoor  0x%x", mem_frame->backdoor);
+  uint32_t kBackdoorExpectedWords[kTestBufferSizeWords];
+  memcpy(kBackdoorExpectedWords, kBackdoorExpectedBytes, kTestBufferSizeBytes);
+
+  CHECK_ARRAYS_EQ(mem_frame->backdoor, kBackdoorExpectedWords,
+                  kTestBufferSizeWords);
+}
+
+static void execute_retention_sram_test(void) {
+  LOG_INFO("Wiping retention sram...");
+  CHECK_DIF_OK(dif_sram_ctrl_wipe(&ret_sram));
+
+  LOG_INFO("Preparing test...");
+  prepare_sram_for_scrambling();
+
+  LOG_INFO("Scrambling...");
+  sram_ctrl_testutils_scramble(&ret_sram);
+
+  // Reset the Ecc error count that lies on the main sram.
+  LOG_INFO("Checking memory...");
+  reference_frame->ecc_error_counter = 0;
+  check_sram_data(scrambling_frame);
+}
+
+/**
+ * Override internal IRQ interrupt service routine to count
+ * the number of integrity exceptions.
+ */
+void ottf_internal_isr(void) {
+  LOG_INFO("%s - %d", __func__, reference_frame->ecc_error_counter);
+  reference_frame->ecc_error_counter++;
+}
+
+static void sync_testbench(void) {
+  // Set WFI status for testbench synchronization,
+  // no actual WFI instruction is issued.
+  test_status_set(kTestStatusInWfi);
+  test_status_set(kTestStatusInTest);
+}
+
+/**
+ * Executes the MAIN SRAM and RET SRAM scrambling test.
+ *   This test:
+ * - Set the retention SRAM address to the Owner space range.
+ * - Set a random address to the main SRAM in between the heap and stack.
+ * - Set the reference memory as the retention SRAM and the scrambling as the
+ * main SRAM.
+ * - Inform the address to the testbench using `INFO_LOG`.
+ * - Prepare the main and retention memory for the test by writing a pattern to
+ * them. In both cases, we write two patterns and double check that only the
+ * second pattern is actually stored in the memory.
+ * - Save the reference and scrambling frames pointers from the registers.
+ * - Request a new scrambling key for the main memory. This will only
+ * re-scramble the main memory - the retention memory will remain intact!
+ * - Restore the reference and scrambling frames pointers to registers.
+ * - The backdoor sequence triggers once the new scrambling key becomes valid,
+ * and writes random, but correctly scrambled and ECC encoded data to the main
+ * memory.
+ * - Copy the contents of the `scrambling_frame` to the `reference_frame` except
+ * the `ecc_error_counter` to be verified later.
+ * - Reset the chip to restore the c runtime.
+ * - We check that the `reference_frame` does not match any of the test
+ * patterns.
+ * - Check the ECC error counter.
+ * - Check that the backdoor written data in the `reference_frame`, matches with
+ * the data supplied by the testbench.
+ * - Pick a random address in the retention SRAM range.
+ * - Set the reference memory as the main SRAM and the scrambling as the ret
+ * SRAM and repeat the test except that it is neither necessary to copy the
+ * `scrambling_frame` to the `reference_frame` nor reset the chip before the
+ * checking.
+ */
+uint32_t main_sram_addr;
+uint32_t ret_sram_addr;
+
+extern uint8_t _stack_start[];
+extern uint8_t _freertos_heap_start[];
+
+bool test_main(void) {
+  CHECK_DIF_OK(dif_rstmgr_init(
+      mmio_region_from_addr(TOP_MATCHA_RSTMGR_AON_BASE_ADDR), &rstmgr));
+
+  CHECK_DIF_OK(dif_sram_ctrl_init(
+      mmio_region_from_addr(TOP_MATCHA_SRAM_CTRL_RET_AON_REGS_BASE_ADDR),
+      &ret_sram));
+
+  CHECK_DIF_OK(dif_flash_ctrl_init_state(
+      &flash_ctrl,
+      mmio_region_from_addr(TOP_MATCHA_FLASH_CTRL_CORE_BASE_ADDR)));
+  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);
+
+  main_sram_addr = OT_ALIGN_MEM(rand_testutils_gen32_range(
+      (uintptr_t)_freertos_heap_start,
+      (uintptr_t)_stack_start - sizeof(scramble_test_frame)));
+
+  // Note: Any other address range in the ret SRAM may be written during the
+  // boot, which will invalidate the test.
+  ret_sram_addr = OT_ALIGN_MEM(kRetSramOwnerAddr);
+
+  scrambling_frame = (scramble_test_frame *)main_sram_addr;
+  reference_frame = (scramble_test_frame *)ret_sram_addr;
+  LOG_INFO("RET_SRAM addr: %x MAIN_SRAM addr: %x", ret_sram_addr,
+           main_sram_addr);
+
+  dif_rstmgr_reset_info_bitfield_t info = rstmgr_testutils_reason_get();
+  if (info == kDifRstmgrResetInfoPor) {
+    sync_testbench();
+    LOG_INFO("First boot, testing main sram");
+    // First boot, start with ret sram.
+    execute_main_sram_test();
+
+  } else if (info == kDifRstmgrResetInfoSw) {
+    // Counter Value
+    // 0 : First reset, run retention mem scramble test
+    // 1 : Second reset which is used to clear the alerts caused by
+    //     retention mem scramble test, return to finish the test
+    uint32_t ret_sram_tested = flash_ctrl_testutils_counter_get(0);
+    if (ret_sram_tested) return true;
+
+    LOG_INFO("Second boot, checking main sram");
+    check_sram_data(reference_frame);
+
+    LOG_INFO("Testing Retention sram");
+    ret_sram_addr = OT_ALIGN_MEM(rand_testutils_gen32_range(
+        kRetSramBaseAddr, kRetRamLastAddr - sizeof(scramble_test_frame)));
+    LOG_INFO("RET_SRAM addr: %x MAIN_SRAM addr: %x", ret_sram_addr,
+             main_sram_addr);
+    sync_testbench();
+
+    scrambling_frame = (scramble_test_frame *)ret_sram_addr;
+    reference_frame = (scramble_test_frame *)main_sram_addr;
+
+    execute_retention_sram_test();
+
+    flash_ctrl_testutils_counter_increment(&flash_ctrl, 0);
+    CHECK_DIF_OK(dif_rstmgr_software_device_reset(&rstmgr));
+
+    // Add delay such that the software reset can take effect
+    busy_spin_micros(100);
+
+    // This should not be reached.
+    LOG_ERROR("This is unreachable since a reset should have been triggered");
+    return false;
+  }
+
+  return false;
+}