[tests] add example top-level concurrency test

This adds an example top-level concurrency test to showcase how to use
the OTTF to launch, and context switch between several concurrent FreeRTOS
tasks.

Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/sw/device/lib/testing/test_framework/ottf_macros.h b/sw/device/lib/testing/test_framework/ottf_macros.h
index 9f7ab88..0be4d0b 100644
--- a/sw/device/lib/testing/test_framework/ottf_macros.h
+++ b/sw/device/lib/testing/test_framework/ottf_macros.h
@@ -9,5 +9,9 @@
 #define OTTF_NV_SCRATCH _non_volatile_scratch_start
 #define OTTF_HALF_WORD_SIZE (OTTF_WORD_SIZE / 2)
 #define OTTF_CONTEXT_SIZE (OTTF_WORD_SIZE * 30)
+#define OTTF_TASK_DELETE_SELF_OR_DIE \
+  ottf_task_delete_self();           \
+  abort();                           \
+  CHECK(false);
 
 #endif  // OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_OTTF_MACROS_H_
diff --git a/sw/device/tests/BUILD b/sw/device/tests/BUILD
index cf968c6..4ebe462 100644
--- a/sw/device/tests/BUILD
+++ b/sw/device/tests/BUILD
@@ -664,6 +664,16 @@
 )
 
 opentitan_functest(
+    name = "example_concurrency_test",
+    srcs = ["example_concurrency_test.c"],
+    deps = [
+        "//sw/device/lib/runtime:log",
+        "//sw/device/lib/testing/test_framework:check",
+        "//sw/device/lib/testing/test_framework:ottf_main",
+    ],
+)
+
+opentitan_functest(
     name = "example_test_from_flash",
     srcs = ["example_test_from_flash.c"],
     deps = [
diff --git a/sw/device/tests/example_concurrency_test.c b/sw/device/tests/example_concurrency_test.c
new file mode 100644
index 0000000..a259a0e
--- /dev/null
+++ b/sw/device/tests/example_concurrency_test.c
@@ -0,0 +1,106 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * This example serves as a starting point for writing software top-level
+ * concurrency tests that use the OpenTitan Test Framework (OTTF), and run at
+ * the flash boot stage. This example is intended to be copied and modified
+ * according to the instructions below.
+ *
+ * This example demonstrates a concurrency test that spawns three FreeRTOS
+ * tasks, in addition to the `test_main` task. Each task is defined using a
+ * separate function that never returns, and deletes itself after executing its
+ * code. Since the priorities of each task are the same, yet higher than the
+ * priority of the "test_main" task, calling `ottf_task_yield()` will switch
+ * control flow between these tasks, until each task deletes itself. Then, the
+ * `test_main` task will continue executing, returning `true` when the overall
+ * test passes, triggering the OTTF to signal test execution has completed.
+ *
+ * Additionally, an example assertion failure is commented out below in `task_3`
+ * to demonstrate how the test will terminate execution immediately upon
+ * encountering said behavior in any task. The test runner (opentitantool) on
+ * Verilator and FPGA platforms is monitoring the UART for a failure message
+ * that gets printed immediately upon an assertion failure. It terminates the
+ * test immediately upon seeing said message. Similarly, in DV, the testbench is
+ * monitoring a specific memory location that gets written to on an assertion
+ * failure.
+ */
+
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/test_framework/check.h"
+#include "sw/device/lib/testing/test_framework/ottf_macros.h"
+#include "sw/device/lib/testing/test_framework/ottf_main.h"
+
+OTTF_DEFINE_TEST_CONFIG(.enable_concurrency = true,
+                        .can_clobber_uart = false, );
+
+static void task_1(void *task_parameters) {
+  // ***************************************************************************
+  // Place test code below.
+  // ***************************************************************************
+  LOG_INFO("Executing %s ...", ottf_task_get_self_name());
+  ottf_task_yield();
+  LOG_INFO("Continuing to execute %s ...", ottf_task_get_self_name());
+
+  // ***************************************************************************
+  // Delete the current task and never return.
+  // ***************************************************************************
+  OTTF_TASK_DELETE_SELF_OR_DIE;
+}
+
+static void task_2(void *task_parameters) {
+  // ***************************************************************************
+  // Place test code below.
+  // ***************************************************************************
+  LOG_INFO("Executing %s ...", ottf_task_get_self_name());
+
+  // ***************************************************************************
+  // Delete the current task and never return.
+  // ***************************************************************************
+  OTTF_TASK_DELETE_SELF_OR_DIE;
+}
+
+static void task_3(void *task_parameters) {
+  // ***************************************************************************
+  // Place test code below.
+  // ***************************************************************************
+  LOG_INFO("Executing %s ...", ottf_task_get_self_name());
+
+  // ***************************************************************************
+  // Uncomment to see the effects of a failed assertion. Delete this when
+  // implementing a test.
+  // ***************************************************************************
+  // CHECK(false, "A failed assertion causes immediate test termination.");
+
+  // ***************************************************************************
+  // Delete the current task and never return.
+  // ***************************************************************************
+  OTTF_TASK_DELETE_SELF_OR_DIE;
+}
+
+bool test_main(void) {
+  // ***************************************************************************
+  // Create the FreeRTOS tasks that will comprise this test. Ensure the priority
+  // levels of each task are higher than the priority of the current "test_main"
+  // task, which is 0.
+  // ***************************************************************************
+  LOG_INFO("Starting to execute %s ...", ottf_task_get_self_name());
+  CHECK(ottf_task_create(task_1, "task_1", kOttfFreeRtosMinStackSize, 1));
+  CHECK(ottf_task_create(task_2, "task_2", kOttfFreeRtosMinStackSize, 1));
+  CHECK(ottf_task_create(task_3, "task_3", kOttfFreeRtosMinStackSize, 1));
+
+  // ***************************************************************************
+  // Yield control flow to the highest priority task in the run queue. Since the
+  // tasks created above all have a higher priority level than the current
+  // "test_main" task, execution will not be returned to the current task until
+  // the above tasks have been deleted.
+  // ***************************************************************************
+  LOG_INFO("Yielding execution to another task.");
+  ottf_task_yield();
+
+  // ***************************************************************************
+  // Return true if the test succeeds. Return false if it should fail.
+  // ***************************************************************************
+  return true;
+}