Merge "Add MACC tests."
diff --git a/scripts/test_runner.py b/scripts/test_runner.py
index 5941830..f9e3c72 100755
--- a/scripts/test_runner.py
+++ b/scripts/test_runner.py
@@ -2,6 +2,7 @@
 """Runs test within Qemu and Renode simulators."""
 import argparse
 import os
+import re
 import sys
 import tempfile
 
@@ -120,10 +121,20 @@
     simulator_class = Simulators[args.simulator]
     simulator = simulator_class(simulator_path, args.elf)
     output = simulator.run(timeout=args.timeout)
+    # mono API generates escape characters at the termination. Need to clean up.
+    # TODO(hcindyl): Remove this when Renode fix the mono call.
+    ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
+    output = ansi_escape.sub("", output)
     print(output)
     failure_strings = ["FAILED", "Exception occurred"]
     if any(x in output for x in failure_strings):
         sys.exit(1)
+    # Grab the return code from the output string with regex
+    # Syntax: "main returned: ", <code> (<hex_code>)
+    return_string = re.compile(
+        r"\"main returned:\s\",(?P<ret_code>\s[0-9]+\s*)")
+    code = return_string.search(output)
+    sys.exit(int(code.group(1)))
 
 
 if __name__ == "__main__":
diff --git a/springbok/crt0.S b/springbok/crt0.S
index d4ce006..68351ce 100644
--- a/springbok/crt0.S
+++ b/springbok/crt0.S
@@ -117,8 +117,25 @@
         # Check the stack, and fix it if it's broken
         jal  ra, _check_stack
 
-        # Save check_stack's return value
-        mv   s1, a0
+        # Was the stack corrupted?
+        beq  a0, zero, 1f
+
+        # The stack was corrupted!
+        # These strings are stored in instruction memory like
+        # this so they can't ever be corrupted.
+        li   t0, 0x63617473 # "stac"
+        li   t1, 0x6f63206b # "k co"
+        li   t2, 0x70757272 # "rrup"
+        li   t3, 0x00646574 # "ted\0"
+        addi sp, sp, -16
+        sw   t0, 0(sp)
+        sw   t1, 4(sp)
+        sw   t2, 8(sp)
+        sw   t3, 12(sp)
+        li   t4, 0 # ERROR logging level
+        .word 0x00A10EFB # simprint t4, sp, a0 (encoded as custom3<func3=0>)
+        addi sp, sp, 16
+1:
 
         # Restore the application's return value
         mv   a0, s0
@@ -139,25 +156,6 @@
         .word 0x00A10EFB # simprint t4, sp, a0 (encoded as custom3<func3=0>)
         addi sp, sp, 16
 
-        # Was the stack corrupted?
-        beq  s1, zero, _finish
-
-        # The stack was corrupted!
-        li   t0, 0x63617473 # "stac"
-        li   t1, 0x6f63206b # "k co"
-        li   t2, 0x70757272 # "rrup"
-        li   t3, 0x00646574 # "ted\0"
-        addi sp, sp, -16
-        sw   t0, 0(sp)
-        sw   t1, 4(sp)
-        sw   t2, 8(sp)
-        sw   t3, 12(sp)
-        mv   a0, s1
-        li   t4, 0 # ERROR logging level
-        .word 0x00A10EFB # simprint t4, sp, a0 (encoded as custom3<func3=0>)
-        addi sp, sp, 16
-        mv   a0, s0
-
 _finish:
         # Store the application's return value onto the stack
         addi sp, sp, -8
@@ -247,6 +245,8 @@
 .weak exception_handler
 exception_handler:
         # Exception occurred
+        # These strings are stored in instruction memory like
+        # this so they can't ever be corrupted.
         li   t0, 0x65637845 # "Exce"
         li   t1, 0x6f697470 # "ptio"
         li   t2, 0x636f206e # "n oc"
diff --git a/springbok/springbok.cpp b/springbok/springbok.cpp
index 95c3941..514eda0 100644
--- a/springbok/springbok.cpp
+++ b/springbok/springbok.cpp
@@ -109,7 +109,8 @@
 extern "C" int float_to_str(const int len, char *buffer, const float value) {
   if (buffer == NULL && len != 0) {
     // Bad inputs
-    springbok_simprint(SPRINGBOK_SIMPRINT_ERROR, "float_to_str handed null buffer with non-zero length! len:", len);
+    LOG_ERROR("float_to_str handed null buffer with non-zero length! len:%d",
+              len);
     return 0;
   }
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e51930f..dbe8669 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -517,3 +517,12 @@
   LINKOPTS
     -Xlinker --defsym=__itcm_length__=128K
 )
+
+vec_cc_test(
+  NAME
+    vmerge_test
+  SRCS
+    vmerge_test.cpp
+  LINKOPTS
+    -Xlinker --defsym=__itcm_length__=128K
+)
diff --git a/tests/vmerge_test.cpp b/tests/vmerge_test.cpp
new file mode 100644
index 0000000..a8f741f
--- /dev/null
+++ b/tests/vmerge_test.cpp
@@ -0,0 +1,106 @@
+#include <stdlib.h>
+
+#include "pw_unit_test/framework.h"
+#include "test_v_helpers.h"
+
+// Test for vmerge.vim and vmerge.vvm instructions.
+namespace vmerge_test {
+namespace {
+
+using namespace test_v_helpers;
+
+uint8_t src_vector_1[MAXVL_BYTES];
+uint8_t src_vector_2[MAXVL_BYTES];
+uint8_t src_mask_vector[MAXVL_BYTES];
+uint8_t dest_vector[MAXVL_BYTES];
+
+class VmergeTest : public ::testing::Test {
+ protected:
+  void SetUp() override { zero_vector_registers(); }
+  void TearDown() override { zero_vector_registers(); }
+};
+
+TEST_F(VmergeTest, vmerge_vim) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+    /* For non narrowing instructions all vectors have same type*/
+    std::tie(vlmax, vl) = vector_test_setup<uint8_t>(
+        VLMUL::LMUL_M1, avl, {dest_vector, src_vector_2, src_mask_vector});
+    if (avl > vlmax) {
+      continue;
+    }
+
+    fill_random_vector<uint8_t>(src_vector_2, vl);
+    fill_random_vector<uint8_t>(src_mask_vector, vl);
+    const int8_t test_val = 12;
+
+    // Load vector registers
+    __asm__ volatile("vle8.v v16, (%0)" : : "r"(src_vector_2));
+    __asm__ volatile("vle8.v v0, (%0)" : : "r"(src_mask_vector));
+
+    // Run target instruction
+    __asm__ volatile("vmerge.vim v24, v16, %[IMM], v0" ::[IMM] "n"(test_val));
+
+    // Store result vector register
+    __asm__ volatile("vse8.v v24, (%0)" : : "r"(dest_vector));
+
+    // Check vector elements
+    constexpr uint32_t kShift = 3;  // shift for uint8_t
+    for (int idx = 0; idx < vl; idx++) {
+      uint32_t mask_idx = idx >> kShift;
+      uint32_t mask_pos = idx & ~(mask_idx << kShift);
+      if (src_mask_vector[mask_idx] & (1 << mask_pos)) {
+        ASSERT_EQ(dest_vector[idx], test_val);
+      } else {
+        ASSERT_EQ(dest_vector[idx], src_vector_2[idx]);
+      }
+    }
+  }
+}
+
+TEST_F(VmergeTest, vmerge_vvm) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+    /* For non narrowing instructions all vectors have same type*/
+    std::tie(vlmax, vl) = vector_test_setup<uint8_t>(
+        VLMUL::LMUL_M1, avl,
+        {dest_vector, src_vector_1, src_vector_2, src_mask_vector});
+    if (avl > vlmax) {
+      continue;
+    }
+
+    fill_random_vector<uint8_t>(src_vector_1, vl);
+    fill_random_vector<uint8_t>(src_vector_2, vl);
+    fill_random_vector<uint8_t>(src_mask_vector, vl);
+
+    // Load vector registers
+    __asm__ volatile("vle8.v v8, (%0)" : : "r"(src_vector_1));
+    __asm__ volatile("vle8.v v16, (%0)" : : "r"(src_vector_2));
+    __asm__ volatile("vle8.v v0, (%0)" : : "r"(src_mask_vector));
+
+    // Run target instruction
+    __asm__ volatile("vmerge.vvm v24, v16, v8, v0");
+
+    // Store result vector register
+    __asm__ volatile("vse8.v v24, (%0)" : : "r"(dest_vector));
+
+    // Check vector elements
+    constexpr uint32_t kShift = 3;  // shift for uint8_t
+    for (int idx = 0; idx < vl; idx++) {
+      uint32_t mask_idx = idx >> kShift;
+      uint32_t mask_pos = idx & ~(mask_idx << kShift);
+      if (src_mask_vector[mask_idx] & (1 << mask_pos)) {
+        ASSERT_EQ(dest_vector[idx], src_vector_1[idx]);
+      } else {
+        ASSERT_EQ(dest_vector[idx], src_vector_2[idx]);
+      }
+    }
+  }
+}
+
+}  // namespace
+}  // namespace vmerge_test