Add test generator using python mako templates.

* Adds templates for OPIVV, OPIVX and OPIVI instruction tests.
* Adds cmake targets for generating test source code.
* Convert vsub and vadd to autogenerated tests.
* Remove vand test.

Change-Id: I1a197894e83e7ae23841e5164eac6e13186f9c09
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b6c65e8..73fbeef 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,5 +1,5 @@
 cmake_minimum_required(VERSION 3.10)
-
+include(vec_cc_generated_test.cmake)
 enable_language(ASM)
 
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/test_runner.py
@@ -15,6 +15,27 @@
       ${VEC_DEFAULT_COPTS}
 )
 
+vec_cc_generated_test(
+  NAME
+    vsub
+  OPFMT
+    OPIVV
+    OPIVX
+  LINKOPTS
+   -Xlinker --defsym=__itcm_length__=128K
+)
+
+vec_cc_generated_test(
+  NAME
+    vadd
+  OPFMT
+    OPIVV
+    OPIVX
+    OPIVI
+  LINKOPTS
+   -Xlinker --defsym=__itcm_length__=256K
+)
+
 vec_cc_test(
   NAME
     vsetvl_test
@@ -48,19 +69,6 @@
 
 vec_cc_test(
   NAME
-    vadd_test
-  SRCS
-    vadd_vi_test.cpp
-    vadd_vx_test.cpp
-    vadd_vv_test.cpp
-  LINKOPTS
-   -Xlinker --defsym=__itcm_length__=256K
-  TIMEOUT
-    40
-)
-
-vec_cc_test(
-  NAME
     vmax_test
   SRCS
     vmax_vx_test.cpp
@@ -71,13 +79,3 @@
     40
 )
 
-vec_cc_test(
-  NAME
-    vand_test
-  SRCS
-    vand_vi_test.cpp
-  LINKOPTS
-   -Xlinker --defsym=__itcm_length__=256K
-  TIMEOUT
-    40
-)
diff --git a/tests/scripts/generate_vector_tests.py b/tests/scripts/generate_vector_tests.py
new file mode 100644
index 0000000..b3e002d
--- /dev/null
+++ b/tests/scripts/generate_vector_tests.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+"""
+Generate tests for Vector Instructions
+"""
+import os
+import logging
+import argparse
+from collections import namedtuple
+from pathlib import Path
+
+from mako.lookup import TemplateLookup
+import mako.exceptions
+
+parser = argparse.ArgumentParser(
+    description='Generate tests for vector instructions.')
+
+parser.add_argument('--template-path', dest='template_path',
+                    help='path to templates', required=True)
+parser.add_argument('-v', '--verbose', help='increase output verbosity',
+                    action='store_true')
+parser.add_argument('--op-code', dest='op_code',
+                    help='Op-code', required=True)
+parser.add_argument('--out-path', dest='out_path',
+                    help='Path to output files', default='.')
+
+template_name_lookup = {
+    'OPIVV':'opivv_test.tpl.cpp',
+    'OPIVI':'opivi_test.tpl.cpp',
+    'OPIVX':'opivx_test.tpl.cpp',
+}
+
+parser.add_argument('--instruction-format',
+    action='append', choices=template_name_lookup.keys(), required=True)
+args = parser.parse_args()
+
+if args.verbose:
+    logging.basicConfig(level=logging.DEBUG)
+
+def main():
+    """ Main routine for generating tests from templates."""
+    mylookup = TemplateLookup(directories=[args.template_path, "."])
+    template_names = [template_name_lookup.get(fmt, None) for fmt in args.instruction_format]
+    template_names = [template for template in template_names if template is not None]
+    template_jobs = []
+    TemplateJob = namedtuple("TemplateJob", "template outfile")
+    Path(args.out_path).mkdir(parents=True, exist_ok=True)
+    for opfmt, template_name in template_name_lookup.items():
+        if not opfmt in args.instruction_format:
+            continue
+        try:
+            template = mylookup.get_template(template_name)
+        except mako.exceptions.TopLevelLookupException:
+            parser.error("Template does not exist %s" % template_name)
+        outfile_name = "%s_%s" % (args.op_code, template_name.replace(".tpl", ""))
+        template_jobs.append(TemplateJob(template, outfile_name))
+
+    for template_job in template_jobs:
+        full_outfile_path = os.path.join(args.out_path, template_job.outfile)
+        with open(full_outfile_path, "w+") as outfile:
+            outfile.write(template_job.template.render(op_code=args.op_code))
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/templates/opivi_test.tpl.cpp b/tests/templates/opivi_test.tpl.cpp
new file mode 100644
index 0000000..40a5d26
--- /dev/null
+++ b/tests/templates/opivi_test.tpl.cpp
@@ -0,0 +1,78 @@
+/* Automatically generated file */
+#include <limits.h>
+#include <riscv_vector.h>
+#include <softrvv.h>
+#include <springbok.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <bit>
+#include <tuple>
+
+#include "pw_unit_test/framework.h"
+#include "test_v_helpers.h"
+
+namespace ${op_code}_vi_test {
+namespace {
+
+using namespace test_v_helpers;
+
+uint8_t src_vector_1[MAXVL_BYTES];
+uint8_t dest_vector[MAXVL_BYTES];
+uint8_t ref_dest_vector[MAXVL_BYTES];
+
+class ${op_code.capitalize()}Test : public ::testing::Test {
+ protected:
+  void SetUp() override { zero_vector_registers(); }
+  void TearDown() override { zero_vector_registers(); }
+};
+% for test_val in [-16, -15, -4, 1, 5, 12, 15]:
+% for sew in [8, 16, 32]:
+% for lmul in [1, 2, 4, 8]:
+<%
+var_type = "int%d_t" % sew
+%>\
+
+TEST_F(${op_code.capitalize()}Test, ${op_code.lower()}_vi${sew}m${lmul}simm5_${("%s" % test_val).replace('-','n')}) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+    const ${var_type} test_val = ${test_val};
+
+    std::tie(vlmax, vl) = vector_test_setup<${var_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1, dest_vector, ref_dest_vector});
+    if (avl > vlmax) {
+      continue;
+    }
+    ${var_type} *ptr_vec_1 = reinterpret_cast<${var_type} *>(src_vector_1);
+    ${var_type} *ptr_dest_vec = reinterpret_cast<${var_type} *>(dest_vector);
+    ${var_type} *ptr_ref_dest_vec = reinterpret_cast<${var_type} *>(ref_dest_vector);
+    // set up values to test up to index of the AVL
+    fill_random_vector<${var_type}>(ptr_vec_1, avl);
+    memset(dest_vector, 0, MAXVL_BYTES);
+    memset(ref_dest_vector, 0, MAXVL_BYTES);
+
+    // Generate reference vector
+    softrvv::${op_code}_vx<${var_type}>(ptr_ref_dest_vec, ptr_vec_1, &test_val, avl);
+
+    // Load vector registers
+    __asm__ volatile("vle${sew}.v v8, (%0)" : : "r"(ptr_vec_1));
+
+    // Run target instruction
+    __asm__ volatile("${op_code}.vi v24, v8, %[SIMM5]" ::[SIMM5] "n"(test_val));
+
+    // Store result vector register
+    __asm__ volatile("vse${sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
+
+    // Check vector elements
+    assert_vec_elem_eq<${var_type}>(vlmax, dest_vector, ref_dest_vector);
+  }
+}
+%endfor
+%endfor
+%endfor
+
+}  // namespace
+}  // namespace ${op_code}_vi_test
diff --git a/tests/templates/opivv_test.tpl.cpp b/tests/templates/opivv_test.tpl.cpp
new file mode 100644
index 0000000..1df0448
--- /dev/null
+++ b/tests/templates/opivv_test.tpl.cpp
@@ -0,0 +1,79 @@
+/* Automatically generated file */
+#include <limits.h>
+#include <riscv_vector.h>
+#include <softrvv.h>
+#include <springbok.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <bit>
+#include <tuple>
+
+#include "pw_unit_test/framework.h"
+#include "test_v_helpers.h"
+
+namespace ${op_code}_vv_test {
+namespace {
+
+using namespace test_v_helpers;
+
+uint8_t src_vector_1[MAXVL_BYTES];
+uint8_t src_vector_2[MAXVL_BYTES];
+uint8_t dest_vector[MAXVL_BYTES];
+uint8_t ref_dest_vector[MAXVL_BYTES];
+
+class ${op_code.capitalize()}Test : public ::testing::Test {
+ protected:
+  void SetUp() override { zero_vector_registers(); }
+  void TearDown() override { zero_vector_registers(); }
+};
+% for sew in [8, 16, 32]:
+% for lmul in [1, 2, 4, 8]:
+<%
+var_type = "int%d_t" % sew
+%>\
+
+TEST_F(${op_code.capitalize()}Test, ${op_code.lower()}_vv${sew}m${lmul}) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+    std::tie(vlmax, vl) = vector_test_setup<${var_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1, src_vector_2, dest_vector, ref_dest_vector});
+    if (avl > vlmax) {
+      continue;
+    }
+    ${var_type} *ptr_vec_1 = reinterpret_cast<${var_type} *>(src_vector_1);
+    ${var_type} *ptr_vec_2 = reinterpret_cast<${var_type} *>(src_vector_2);
+    ${var_type} *ptr_dest_vec = reinterpret_cast<${var_type} *>(dest_vector);
+    ${var_type} *ptr_ref_dest_vec = reinterpret_cast<${var_type} *>(ref_dest_vector);
+
+    // set up values to test up to index of the AVL
+    fill_random_vector<${var_type}>(ptr_vec_1, avl);
+    fill_random_vector<${var_type}>(ptr_vec_2, avl);
+    memset(dest_vector, 0, MAXVL_BYTES);
+    memset(ref_dest_vector, 0, MAXVL_BYTES);
+
+    // Generate reference vector
+    softrvv::${op_code}_vv<${var_type}>(ptr_ref_dest_vec, ptr_vec_2, ptr_vec_1, avl);
+
+    // Load vector registers
+    __asm__ volatile("vle${sew}.v v8, (%0)" : : "r"(ptr_vec_1));
+    __asm__ volatile("vle${sew}.v v16, (%0)" : : "r"(ptr_vec_2));
+
+    // Run target instruction
+    __asm__ volatile("${op_code}.vv v24, v16, v8");
+
+    // Store result vector register
+    __asm__ volatile("vse${sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
+
+    // Check vector elements
+    assert_vec_elem_eq<${var_type}>(vlmax, dest_vector, ref_dest_vector);
+  }
+}
+%endfor
+%endfor
+
+}  // namespace
+}  // namespace ${op_code}_vv_test
diff --git a/tests/templates/opivx_test.tpl.cpp b/tests/templates/opivx_test.tpl.cpp
new file mode 100644
index 0000000..2476708
--- /dev/null
+++ b/tests/templates/opivx_test.tpl.cpp
@@ -0,0 +1,75 @@
+/* Automatically generated file */
+#include <limits.h>
+#include <riscv_vector.h>
+#include <softrvv.h>
+#include <springbok.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <bit>
+#include <tuple>
+
+#include "pw_unit_test/framework.h"
+#include "test_v_helpers.h"
+
+namespace ${op_code}_vx_test {
+namespace {
+
+using namespace test_v_helpers;
+
+uint8_t src_vector_1[MAXVL_BYTES];
+uint8_t dest_vector[MAXVL_BYTES];
+uint8_t ref_dest_vector[MAXVL_BYTES];
+
+class ${op_code.capitalize()}Test : public ::testing::Test {
+ protected:
+  void SetUp() override { zero_vector_registers(); }
+  void TearDown() override { zero_vector_registers(); }
+};
+% for sew in [8, 16, 32]:
+% for lmul in [1, 2, 4, 8]:
+<%
+var_type = "int%d_t" % sew
+%>\
+
+TEST_F(${op_code.capitalize()}Test, ${op_code.lower()}_vx${sew}m${lmul}) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+    std::tie(vlmax, vl) = vector_test_setup<${var_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1, dest_vector, ref_dest_vector});
+    if (avl > vlmax) {
+      continue;
+    }
+    ${var_type} *ptr_vec_1 = reinterpret_cast<${var_type} *>(src_vector_1);
+    ${var_type} *ptr_dest_vec = reinterpret_cast<${var_type} *>(dest_vector);
+    ${var_type} *ptr_ref_dest_vec = reinterpret_cast<${var_type} *>(ref_dest_vector);
+    ${var_type} test_val = static_cast<${var_type}>(rand());
+    // set up values to test up to index of the AVL
+    fill_random_vector<${var_type}>(ptr_vec_1, avl);
+    memset(dest_vector, 0, MAXVL_BYTES);
+    memset(ref_dest_vector, 0, MAXVL_BYTES);
+
+    // Generate reference vector
+    softrvv::${op_code}_vx<${var_type}>(ptr_ref_dest_vec, ptr_vec_1, &test_val, avl);
+
+    // Load vector registers
+    __asm__ volatile("vle${sew}.v v8, (%0)" : : "r"(ptr_vec_1));
+
+    // Run target instruction
+    __asm__ volatile("${op_code}.vx v24, v8, %[RS1]" ::[RS1] "r"(test_val));
+
+    // Store result vector register
+    __asm__ volatile("vse${sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
+
+    // Check vector elements
+    assert_vec_elem_eq<${var_type}>(vlmax, dest_vector, ref_dest_vector);
+  }
+}
+%endfor
+%endfor
+
+}  // namespace
+}  // namespace ${op_code}_vx_test
diff --git a/tests/vadd_vi_test.cpp b/tests/vadd_vi_test.cpp
deleted file mode 100644
index 389d862..0000000
--- a/tests/vadd_vi_test.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-#include <riscv_vector.h>
-#include <softrvv.h>
-#include <springbok.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <bit>
-#include <tuple>
-
-#include "pw_unit_test/framework.h"
-#include "test_v_helpers.h"
-
-namespace vadd_vi_test {
-namespace {
-
-using namespace test_v_helpers;
-
-uint8_t test_vector_1[MAXVL_BYTES];
-uint8_t reference_vector_1[MAXVL_BYTES];
-
-class VaddViTest : public ::testing::Test {
- protected:
-  void SetUp() override { zero_vector_registers(); }
-  void TearDown() override { zero_vector_registers(); }
-};
-
-// Below is a non-macro version of the test for more convenient debugging.
-// Remove the "DISABLED_" prefix to enable this test for debugging.
-TEST_F(VaddViTest, DISABLED_vadd_vi_demo) {
-  for (int i = 0; i < AVL_COUNT; i++) {
-    int32_t avl = AVLS[i];
-    int vlmax;
-    int vl;
-    std::tie(vlmax, vl) = vector_test_setup<int8_t>(
-        VLMUL::LMUL_M1, avl, {test_vector_1, reference_vector_1});
-    if (avl > vlmax) {
-      continue;
-    }
-    int8_t *ptr_vec_1 = reinterpret_cast<int8_t *>(test_vector_1);
-    int8_t *ptr_ref_vec_1 = reinterpret_cast<int8_t *>(reference_vector_1);
-    const int8_t test_val_1 = 1;
-    const int8_t test_val_2 = -3;
-    __asm__ volatile("vadd.vi v8, v8, %[SIMM5]" ::[SIMM5] "n"(test_val_1));
-    __asm__ volatile("vadd.vi v8, v8, %[SIMM5]" ::[SIMM5] "n"(test_val_2));
-    softrvv::vadd_vi<int8_t>(ptr_ref_vec_1, ptr_ref_vec_1, test_val_1, avl);
-    softrvv::vadd_vi<int8_t>(ptr_ref_vec_1, ptr_ref_vec_1, test_val_2, avl);
-    __asm__ volatile("vse8.v v8, (%0)" : : "r"(ptr_vec_1));
-    assert_vec_elem_eq<int8_t>(vlmax, test_vector_1, reference_vector_1);
-  }
-}
-
-#define DEFINE_TEST_VADD_VI(_SEW_, _LMUL_, TEST_VAL_1, TEST_VAL_2)            \
-  TEST_F(VaddViTest, vadd_vi##_SEW_##m##_LMUL_) {                             \
-    for (int i = 0; i < AVL_COUNT; i++) {                                     \
-      int32_t avl = AVLS[i];                                                  \
-      int vlmax;                                                              \
-      int vl;                                                                 \
-      std::tie(vlmax, vl) = vector_test_setup<int##_SEW_##_t>(                \
-          VLMUL::LMUL_M##_LMUL_, avl, {test_vector_1, reference_vector_1});   \
-      if (avl > vlmax) {                                                      \
-        continue;                                                             \
-      }                                                                       \
-      int##_SEW_##_t *ptr_vec_1 =                                             \
-          reinterpret_cast<int##_SEW_##_t *>(test_vector_1);                  \
-      int##_SEW_##_t *ptr_ref_vec_1 =                                         \
-          reinterpret_cast<int##_SEW_##_t *>(reference_vector_1);             \
-      const int##_SEW_##_t test_val_1 = TEST_VAL_1;                           \
-      const int##_SEW_##_t test_val_2 = TEST_VAL_2;                           \
-      __asm__ volatile("vadd.vi v8, v8, %[SIMM5]" ::[SIMM5] "n"(test_val_1)); \
-      __asm__ volatile("vadd.vi v8, v8, %[SIMM5]" ::[SIMM5] "n"(test_val_2)); \
-      softrvv::vadd_vi<int##_SEW_##_t>(ptr_ref_vec_1, ptr_ref_vec_1,          \
-                                       test_val_1, avl);                      \
-      softrvv::vadd_vi<int##_SEW_##_t>(ptr_ref_vec_1, ptr_ref_vec_1,          \
-                                       test_val_2, avl);                      \
-      __asm__ volatile("vse" #_SEW_ ".v v8, (%0)" : : "r"(ptr_vec_1));        \
-      assert_vec_elem_eq<int##_SEW_##_t>(vlmax, test_vector_1,                \
-                                         reference_vector_1);                 \
-    }                                                                         \
-  }
-
-// TODO(gkielian): modify macro to permit more than one test per sew/lmul pair
-DEFINE_TEST_VADD_VI(8, 1, -16, 15)
-DEFINE_TEST_VADD_VI(8, 2, -2, 3)
-DEFINE_TEST_VADD_VI(8, 4, 15, 15)
-DEFINE_TEST_VADD_VI(8, 8, -15, -15)
-
-DEFINE_TEST_VADD_VI(16, 1, -16, 15)
-DEFINE_TEST_VADD_VI(16, 2, -2, 3)
-DEFINE_TEST_VADD_VI(16, 4, 15, 15)
-DEFINE_TEST_VADD_VI(16, 8, -15, -15)
-
-DEFINE_TEST_VADD_VI(32, 1, -16, 15)
-DEFINE_TEST_VADD_VI(32, 2, -2, 3)
-DEFINE_TEST_VADD_VI(32, 4, 15, 15)
-DEFINE_TEST_VADD_VI(32, 8, -15, -15)
-
-}  // namespace
-}  // namespace vadd_vi_test
diff --git a/tests/vadd_vv_test.cpp b/tests/vadd_vv_test.cpp
deleted file mode 100644
index f1b7a00..0000000
--- a/tests/vadd_vv_test.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-#include <limits.h>
-#include <riscv_vector.h>
-#include <softrvv.h>
-#include <springbok.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <bit>
-#include <tuple>
-
-#include "pw_unit_test/framework.h"
-#include "test_v_helpers.h"
-
-namespace vadd_vv_test {
-namespace {
-
-using namespace test_v_helpers;
-
-uint8_t test_vector_1[MAXVL_BYTES];
-uint8_t reference_vector_1[MAXVL_BYTES];
-
-class VaddVxTest : public ::testing::Test {
- protected:
-  void SetUp() override { zero_vector_registers(); }
-  void TearDown() override { zero_vector_registers(); }
-};
-
-// Below is a demo Test for convenient debugging
-// Currently disabled, so remove "DISABLED_" prefix to enable this test
-TEST_F(VaddVxTest, DISABLED_vadd_vv_demo) {
-  for (int i = 0; i < AVL_COUNT; i++) {
-    int32_t avl = AVLS[i];
-    int vlmax;
-    int vl;
-    std::tie(vlmax, vl) = vector_test_setup<int8_t>(
-        VLMUL::LMUL_M1, avl, {test_vector_1, reference_vector_1});
-    if (avl > vlmax) {
-      continue;
-    }
-    int8_t *ptr_vec_1 = reinterpret_cast<int8_t *>(test_vector_1);
-    int8_t *ptr_ref_vec_1 = reinterpret_cast<int8_t *>(reference_vector_1);
-
-    // set up values to test up to index of the AVL
-    for (int idx = 0; idx < vl; idx++) {
-      // restrict values to valid int8_t range
-      ptr_vec_1[idx] = idx % (INT8_MAX) + (INT8_MIN);
-      reference_vector_1[idx] = ptr_vec_1[idx];
-    }
-    __asm__ volatile("vle8.v v8, (%0)" : : "r"(ptr_vec_1));
-    __asm__ volatile("vadd.vv v8, v8, v8");
-    softrvv::vadd_vv<int8_t>(ptr_ref_vec_1, ptr_ref_vec_1, ptr_ref_vec_1, avl);
-    __asm__ volatile("vse8.v v8, (%0)" : : "r"(ptr_vec_1));
-    assert_vec_elem_eq<int8_t>(vlmax, test_vector_1, reference_vector_1);
-  }
-}
-
-#define DEFINE_TEST_VADD_VV(_SEW_, _LMUL_)                                  \
-  TEST_F(VaddVxTest, vadd_vv##_SEW_##m##_LMUL_) {                           \
-    for (int i = 0; i < AVL_COUNT; i++) {                                   \
-      int32_t avl = AVLS[i];                                                \
-      int vlmax;                                                            \
-      int vl;                                                               \
-      std::tie(vlmax, vl) = vector_test_setup<int##_SEW_##_t>(              \
-          VLMUL::LMUL_M##_LMUL_, avl, {test_vector_1, reference_vector_1}); \
-      if (avl > vlmax) {                                                    \
-        continue;                                                           \
-      }                                                                     \
-      int##_SEW_##_t *ptr_vec_1 =                                           \
-          reinterpret_cast<int##_SEW_##_t *>(test_vector_1);                \
-      int##_SEW_##_t *ptr_ref_vec_1 =                                       \
-          reinterpret_cast<int##_SEW_##_t *>(reference_vector_1);           \
-                                                                            \
-      for (long long idx = 0; idx < avl; idx++) {                           \
-        ptr_vec_1[idx] = idx % INT##_SEW_##_MAX + INT##_SEW_##_MIN;         \
-        ptr_ref_vec_1[idx] = ptr_vec_1[idx];                                \
-      }                                                                     \
-                                                                            \
-      __asm__ volatile("vle" #_SEW_ ".v v8, (%0)" : : "r"(ptr_vec_1));      \
-      __asm__ volatile("vadd.vv v8, v8, v8");                               \
-      softrvv::vadd_vv<int##_SEW_##_t>(ptr_ref_vec_1, ptr_ref_vec_1,        \
-                                       ptr_ref_vec_1, avl);                 \
-      __asm__ volatile("vse" #_SEW_ ".v v8, (%0)" : : "r"(ptr_vec_1));      \
-      assert_vec_elem_eq<int##_SEW_##_t>(vlmax, test_vector_1,              \
-                                         reference_vector_1);               \
-    }                                                                       \
-  }  // namespace
-
-// TODO(gkielian): modify macro to permit more than one test per sew/lmul pair
-DEFINE_TEST_VADD_VV(8, 1)
-DEFINE_TEST_VADD_VV(8, 2)
-DEFINE_TEST_VADD_VV(8, 4)
-DEFINE_TEST_VADD_VV(8, 8)
-
-DEFINE_TEST_VADD_VV(16, 1)
-DEFINE_TEST_VADD_VV(16, 2)
-DEFINE_TEST_VADD_VV(16, 4)
-DEFINE_TEST_VADD_VV(16, 8)
-
-DEFINE_TEST_VADD_VV(32, 1)
-DEFINE_TEST_VADD_VV(32, 2)
-DEFINE_TEST_VADD_VV(32, 4)
-DEFINE_TEST_VADD_VV(32, 8)
-
-}  // namespace
-}  // namespace vadd_vv_test
diff --git a/tests/vadd_vx_test.cpp b/tests/vadd_vx_test.cpp
deleted file mode 100644
index 88ae686..0000000
--- a/tests/vadd_vx_test.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-#include <limits.h>
-#include <riscv_vector.h>
-#include <softrvv.h>
-#include <springbok.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <bit>
-#include <tuple>
-
-#include "pw_unit_test/framework.h"
-#include "test_v_helpers.h"
-
-namespace vadd_vx_test {
-namespace {
-
-using namespace test_v_helpers;
-
-uint8_t test_vector_1[MAXVL_BYTES];
-uint8_t reference_vector_1[MAXVL_BYTES];
-
-class VaddVxTest : public ::testing::Test {
- protected:
-  void SetUp() override { zero_vector_registers(); }
-  void TearDown() override { zero_vector_registers(); }
-};
-
-// Below is a non-macro version of the test for more convenient debugging.
-// Remove the "DISABLED_" prefix to enable this test for debugging.
-TEST_F(VaddVxTest, DISABLED_vadd_vx_demo) {
-  for (int i = 0; i < AVL_COUNT; i++) {
-    int32_t avl = AVLS[i];
-    int vlmax;
-    int vl;
-    std::tie(vlmax, vl) = vector_test_setup<int8_t>(
-        VLMUL::LMUL_M1, avl, {test_vector_1, reference_vector_1});
-    if (avl > vlmax) {
-      continue;
-    }
-    int8_t *ptr_vec_1 = reinterpret_cast<int8_t *>(test_vector_1);
-    int8_t *ptr_ref_vec_1 = reinterpret_cast<int8_t *>(reference_vector_1);
-    int8_t test_val = 8;
-    __asm__ volatile("vadd.vx v8, v8, %[RS1]" ::[RS1] "r"(test_val));
-    softrvv::vadd_vx<int8_t>(ptr_ref_vec_1, ptr_ref_vec_1, &test_val, avl);
-    __asm__ volatile("vse8.v v8, (%0)" : : "r"(ptr_vec_1));
-    assert_vec_elem_eq<int8_t>(vlmax, test_vector_1, reference_vector_1);
-  }
-}
-
-#define DEFINE_TEST_VADD_VX(_SEW_, _LMUL_, TEST_VAL)                        \
-  TEST_F(VaddVxTest, vadd_vx##_SEW_##m##_LMUL_) {                           \
-    for (int i = 0; i < AVL_COUNT; i++) {                                   \
-      int32_t avl = AVLS[i];                                                \
-      int vlmax;                                                            \
-      int vl;                                                               \
-      std::tie(vlmax, vl) = vector_test_setup<int##_SEW_##_t>(              \
-          VLMUL::LMUL_M##_LMUL_, avl, {test_vector_1, reference_vector_1}); \
-      if (avl > vlmax) {                                                    \
-        continue;                                                           \
-      }                                                                     \
-      int##_SEW_##_t *ptr_vec_1 =                                           \
-          reinterpret_cast<int##_SEW_##_t *>(test_vector_1);                \
-      int##_SEW_##_t *ptr_ref_vec_1 =                                       \
-          reinterpret_cast<int##_SEW_##_t *>(reference_vector_1);           \
-      const int##_SEW_##_t test_val = TEST_VAL;                             \
-      __asm__ volatile("vadd.vx v8, v8, %[RS1]" ::[RS1] "r"(test_val));     \
-      softrvv::vadd_vx<int##_SEW_##_t>(ptr_ref_vec_1, ptr_ref_vec_1,        \
-                                       &test_val, avl);                     \
-      __asm__ volatile("vse" #_SEW_ ".v v8, (%0)" : : "r"(ptr_vec_1));      \
-      assert_vec_elem_eq<int##_SEW_##_t>(vlmax, test_vector_1,              \
-                                         reference_vector_1);               \
-    }                                                                       \
-  }
-
-// TODO(gkielian): modify macro to permit more than one test per sew/lmul pair
-DEFINE_TEST_VADD_VX(8, 1, INT8_MIN)
-DEFINE_TEST_VADD_VX(8, 2, INT8_MAX)
-DEFINE_TEST_VADD_VX(8, 4, -1)
-DEFINE_TEST_VADD_VX(8, 8, 2)
-
-DEFINE_TEST_VADD_VX(16, 1, INT16_MIN)
-DEFINE_TEST_VADD_VX(16, 2, INT16_MAX)
-DEFINE_TEST_VADD_VX(16, 4, -1)
-DEFINE_TEST_VADD_VX(16, 8, 2)
-
-DEFINE_TEST_VADD_VX(32, 1, INT32_MIN)
-DEFINE_TEST_VADD_VX(32, 2, INT32_MAX)
-DEFINE_TEST_VADD_VX(32, 4, -1)
-DEFINE_TEST_VADD_VX(32, 8, 2)
-
-}  // namespace
-}  // namespace vadd_vx_test
diff --git a/tests/vand_vi_test.cpp b/tests/vand_vi_test.cpp
deleted file mode 100644
index 9d53f97..0000000
--- a/tests/vand_vi_test.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-#include <limits.h>
-#include <riscv_vector.h>
-#include <springbok.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <bit>
-#include <tuple>
-
-#include "pw_unit_test/framework.h"
-#include "test_v_helpers.h"
-
-namespace vand_vi_test {
-namespace {
-
-using namespace test_v_helpers;
-
-uint8_t test_vector_1[MAXVL_BYTES];
-uint8_t test_vector_2[MAXVL_BYTES];
-
-class VandViTest : public ::testing::Test {
- protected:
-  void SetUp() override { zero_vector_registers(); }
-  void TearDown() override { zero_vector_registers(); }
-};
-
-// Below is a non-macro version of the test for more convenient debugging.
-// Remove the "DISABLED_" prefix to enable this test for debugging.
-TEST_F(VandViTest, DISABLED_vand_vi_demo) {
-  for (int i = 0; i < AVL_COUNT; i++) {
-    int32_t avl = AVLS[i];
-    int vlmax;
-    int vl;
-    std::tie(vlmax, vl) = vector_test_setup<int8_t>(
-        VLMUL::LMUL_M1, avl, test_vector_1, test_vector_2);
-    if (avl > vlmax) {
-      continue;
-    }
-    int8_t *ptr_vec_1 = reinterpret_cast<int8_t *>(test_vector_1);
-    int8_t *ptr_vec_2 = reinterpret_cast<int8_t *>(test_vector_2);
-    const int8_t test_val_1 = INT8_MIN;
-    const int8_t test_val_2 = 15;  // range of SIMM5 is [-16, 15]
-    for (int i = 0; i < vl; i++) {
-      ptr_vec_1[i] = test_val_1;
-    }
-    __asm__ volatile("vle8.v v8, (%0)" : : "r"(ptr_vec_1));
-    __asm__ volatile("vand.vi v8, v8, %[SIMM5]" ::[SIMM5] "n"(test_val_2));
-    for (int i = 0; i < vl; i++) {
-      ptr_vec_1[i] = test_val_1 & test_val_2;
-    }
-    __asm__ volatile("vse8.v v8, (%0)" : : "r"(ptr_vec_2));
-    assert_vec_elem_eq<int8_t>(vlmax, test_vector_1, test_vector_2);
-  }
-}
-
-#define DEFINE_TEST_VAND_VI(_SEW_, _LMUL_, TEST_VAL_1, TEST_VAL_2)             \
-  TEST_F(VandViTest, vand_vi##_SEW_##m##_LMUL_) {                              \
-    for (int i = 0; i < AVL_COUNT; i++) {                                      \
-      int32_t avl = AVLS[i];                                                   \
-      int vlmax;                                                               \
-      int vl;                                                                  \
-      std::tie(vlmax, vl) = vector_test_setup<int##_SEW_##_t>(                 \
-          VLMUL::LMUL_M##_LMUL_, avl, test_vector_1, test_vector_2);           \
-      if (avl > vlmax) {                                                       \
-        continue;                                                              \
-      }                                                                        \
-      int##_SEW_##_t *ptr_vec_1 =                                              \
-          reinterpret_cast<int##_SEW_##_t *>(test_vector_1);                   \
-      int##_SEW_##_t *ptr_vec_2 =                                              \
-          reinterpret_cast<int##_SEW_##_t *>(test_vector_2);                   \
-      const int##_SEW_##_t test_val_1 = TEST_VAL_1;                            \
-      const int##_SEW_##_t test_val_2 = TEST_VAL_2;                            \
-      for (int i = 0; i < vl; i++) {                                           \
-        ptr_vec_1[i] = test_val_1;                                             \
-      }                                                                        \
-      __asm__ volatile("vle" #_SEW_ ".v v8, (%0)" : : "r"(ptr_vec_1));         \
-      __asm__ volatile("vand.vi v8, v8, %[SIMM5]" ::[SIMM5] "n"(test_val_2));  \
-      for (int i = 0; i < vl; i++) {                                           \
-        ptr_vec_1[i] = test_val_1 & test_val_2;                                \
-      }                                                                        \
-      __asm__ volatile("vse" #_SEW_ ".v v8, (%0)" : : "r"(ptr_vec_2));         \
-      assert_vec_elem_eq<int##_SEW_##_t>(vlmax, test_vector_1, test_vector_2); \
-    }                                                                          \
-  }
-
-// TODO(gkielian): modify macro to permit more than one test per sew/lmul pair
-
-// Macro Usage Note:
-// First value just needs to be within SEW range
-// Second value needs to be within range [-16, 15] (SIMM5 range)
-DEFINE_TEST_VAND_VI(8, 1, INT8_MIN, 15)
-DEFINE_TEST_VAND_VI(8, 2, INT8_MAX, 3)
-DEFINE_TEST_VAND_VI(8, 4, -1, 15)
-DEFINE_TEST_VAND_VI(8, 8, 0, -16)
-
-DEFINE_TEST_VAND_VI(16, 1, INT16_MIN, 15)
-DEFINE_TEST_VAND_VI(16, 2, INT16_MAX, 3)
-DEFINE_TEST_VAND_VI(16, 4, -1, 15)
-DEFINE_TEST_VAND_VI(16, 8, 0, -16)
-
-DEFINE_TEST_VAND_VI(32, 1, INT32_MIN, 15)
-DEFINE_TEST_VAND_VI(32, 2, INT32_MAX, 3)
-DEFINE_TEST_VAND_VI(32, 4, -1, 15)
-DEFINE_TEST_VAND_VI(32, 8, 0, -16)
-
-}  // namespace
-}  // namespace vand_vi_test
-
diff --git a/tests/vec_cc_generated_test.cmake b/tests/vec_cc_generated_test.cmake
new file mode 100644
index 0000000..43a10ff
--- /dev/null
+++ b/tests/vec_cc_generated_test.cmake
@@ -0,0 +1,39 @@
+function(vec_cc_generated_test)
+  cmake_parse_arguments(
+    _RULE
+    ""
+    "NAME"
+    "OPFMT;LINKOPTS;TIMEOUT"
+    ${ARGN}
+  )
+
+set(_OPCODE "${_RULE_NAME}")
+
+foreach(_OPFMT ${_RULE_OPFMT})
+string(TOLOWER ${_OPFMT} _LOWER_OPFMT)
+set(_TEST_SRC ${CMAKE_CURRENT_BINARY_DIR}/generated/${_OPCODE}/${_OPCODE}_${_LOWER_OPFMT}_test.cpp)
+add_custom_command(
+    OUTPUT
+      ${_TEST_SRC}
+    DEPENDS
+     ${CMAKE_CURRENT_SOURCE_DIR}/templates/${_LOWER_OPFMT}_test.tpl.cpp
+    COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_vector_tests.py
+      --template-path=${CMAKE_CURRENT_SOURCE_DIR}/templates/
+      --instruction-format ${_OPFMT}
+      --op-code ${_OPCODE}
+      --out-path=${CMAKE_CURRENT_BINARY_DIR}/generated/${_OPCODE}
+)
+list (APPEND _TEST_SRCS "${_TEST_SRC}")
+endforeach()
+
+vec_cc_test(
+  NAME
+    ${_OPCODE}_test
+  SRCS
+    ${_TEST_SRCS}
+  LINKOPTS
+    ${_RULE_LINKOPTS}
+  TIMEOUT
+    ${_RULE_TIMEOUT}
+)
+endfunction()