Add support for vnsrl and narrowing ops.

* vnsrl is a narrowing op, which required template changes to support
varying type widths.
* Add/modify vec_test_helpers module to support narrowing ops.
* Add softrvv vnsrl implementation and test.
* Add a compile time check to softrvv_vnsrl for SEW of dest is 1/2 SEW src2.
* Add vnsrl test.
* Modify softrvv templates for changes in vec_test_helpers module.

Change-Id: I5889200ea36e213dee9a051ae726ed3bf0480d3e
diff --git a/scripts/vec_test_helpers/__init__.py b/scripts/vec_test_helpers/__init__.py
index 10511f8..7da121f 100644
--- a/scripts/vec_test_helpers/__init__.py
+++ b/scripts/vec_test_helpers/__init__.py
@@ -2,47 +2,225 @@
 
 This module is for reusable helper functions used by the templates.
 """
+import collections
+import enum
+import re
 import numpy as np
 
-def is_widening(op_code):
-    """Check if a particular op_code is a widening type."""
-    return op_code[1] == 'w'
+class VecTemplateHelper:
+    """Given op and sew provide template with necessary parameters."""
+    class OperandType(enum.Enum):
+        """RISC-V V operand type options."""
+        VECTOR = enum.auto()
+        SCALAR = enum.auto()
+        IMMEDIATE = enum.auto()
 
-def is_unsigned(op_code):
-    """Check if a particular op_code is a unsigned type."""
-    return op_code[-1] == 'u'
+    class OperandWidth(enum.Enum):
+        """RISC-V V operand width type option."""
+        STANDARD = enum.auto()
+        WIDENING = enum.auto()
+        NARROWING = enum.auto()
 
-def get_sews(op_code):
-    """Given an op_code return a list of valid element widths."""
-    return [8, 16] if is_widening(op_code) else [8, 16, 32]
+    mnemonic_suffix = {
+        OperandType.VECTOR : {
+            OperandWidth.STANDARD: "vv",
+            OperandWidth.WIDENING: "vv",
+            OperandWidth.NARROWING: "wv",
+        },
+        OperandType.SCALAR : {
+            OperandWidth.STANDARD: "vx",
+            OperandWidth.WIDENING: "vx",
+            OperandWidth.NARROWING: "wx",
+        },
+        OperandType.IMMEDIATE : {
+            OperandWidth.STANDARD: "vi",
+            OperandWidth.WIDENING: "vi",
+            OperandWidth.NARROWING: "wi",
+        },
+    }
 
-def get_lmuls(op_code):
-    """Given an op_code return an iterable if valid lmuls."""
-    return [1, 2, 4] if is_widening(op_code) else [1, 2, 4, 8]
+    """Helper class for providing params for use in templates"""
+    def __init__(self, op_code, sew=None):
+        self._op_code = op_code
+        self._sew = sew
+        self.force_unsigned = False
+        self.signed_np_types = {
+            8:np.int8,
+            16:np.int16,
+            32:np.int32}
+        self.unsigned_np_types = {
+            8:np.uint8,
+            16:np.uint16,
+            32:np.uint32}
 
-def get_dest_type(op_code, sew):
-    """Return a destination type for a op_code and element width."""
-    type_fmt = "%sint%d_t"
-    sign_type  = "u" if is_unsigned(op_code) else ""
-    dest_sew = sew * 2 if is_widening(op_code) else sew
-    return type_fmt % (sign_type, dest_sew)
+    @property
+    def op_code(self):
+        """Return the op_code."""
+        if self._op_code is None:
+            raise ValueError("SEW was not set.")
+        return self._op_code
 
-def get_src_type(op_code, sew):
-    """Return a source type for an op_code and element width."""
-    type_fmt = "%sint%d_t"
-    sign_type = "u" if is_unsigned(op_code) else ""
-    return type_fmt % (sign_type, sew)
+    @op_code.setter
+    def op_code(self, value):
+        """Set the op_code"""
+        self._op_code = value
 
-def get_ref_opcode(op_code):
-    """Return the name of the reference code in the softrvv library."""
-    return op_code[:-1] if is_unsigned(op_code) else op_code
+    @property
+    def sew(self):
+        """Return the selected element width."""
+        if self._sew is None:
+            raise ValueError("SEW was not set.")
+        return self._sew
 
-def get_imms(op_code):
-    """Return a list of valid immediate values for a op code."""
-    if op_code in ['vsll', 'vsrl', 'vsra']:
-        # Left and right shift immediates must be [0,31]
-        return np.linspace(0, 31, 8, dtype=np.int32)
-    else:
+    @sew.setter
+    def sew(self, value):
+        """Set the selected element width."""
+        if not value in (8, 16, 32):
+            raise ValueError("Invalid SEW")
+        self._sew = value
+
+    def is_widening(self):
+        """Check if a particular op_code is a widening type."""
+        return self.op_code[1] == 'w'
+
+    def is_narrowing(self):
+        """Check if a particular op_code is a narrowing type."""
+        return self.op_code[1] == 'n'
+
+    def is_unsigned(self):
+        """Check if a particular op_code is a unsigned type."""
+        return self.op_code[-1] == 'u'
+
+    def get_sews(self):
+        """Given an op_code return a list of valid element widths."""
+        if self.is_widening() or self.is_narrowing():
+            return [8, 16]
+        return [8, 16, 32]
+
+    def get_sew_sizes(self):
+        """Return size of types."""
+        dest_sew = self.sew
+        src2_sew = self.sew
+        src1_sew = self.sew
+        imm_sew = self.sew
+        if self.is_narrowing():
+            src2_sew = self.sew * 2
+        elif self.is_widening():
+            dest_sew = self.sew * 2
+        return dest_sew, src2_sew, src1_sew, imm_sew
+
+    def get_var_types(self):
+        """Return types for an op_code and element width."""
+        VarTypes = collections.namedtuple(
+            "VarTypes",
+            ('dest_type', 'src2_type', 'src1_type', 'imm_type'))
+        type_fmt = "%sint%d_t"
+        sign_type  = "u" if self.is_unsigned() or self.force_unsigned  else ""
+        dest_sew, src2_sew, src1_sew, imm_sew = self.get_sew_sizes()
+        dest_type = type_fmt % (sign_type, dest_sew)
+        src1_type = type_fmt % (sign_type, src1_sew)
+        src2_type = type_fmt % (sign_type, src2_sew)
+        imm_type = type_fmt % (sign_type, imm_sew)
+        var_types = VarTypes(dest_type, src2_type, src1_type, imm_type)
+        return var_types
+
+    def get_mnemonic(self, operand_type):
+        """Generate the correct mnemonic given a opcode and operand type."""
+        operand_width = self.OperandWidth.STANDARD
+        if self.is_narrowing():
+            operand_width = self.OperandWidth.NARROWING
+        elif self.is_widening():
+            operand_width = self.OperandWidth.WIDENING
+        op_suffix = self.mnemonic_suffix[operand_type][operand_width]
+        return "%s.%s" % (self.op_code, op_suffix)
+
+    def get_lmuls(self):
+        """Given an op_code return an iterable of valid lmuls."""
+        if self.is_widening() or self.is_narrowing():
+            return [1, 2, 4]
+        return [1, 2, 4, 8]
+
+    @staticmethod
+    def get_sew_from_dtype(dtype):
+        """Extract the selected element width from a data type."""
+        match = re.match(r"[a-z]+(?P<sew>[\d]+)", dtype)
+        return int(match['sew'])
+
+    def get_softrvv_template_data_type(self):
+        """Return types """
+        var_types = self.get_var_types()
+        if self.is_narrowing() or self.is_widening():
+            return "%s, %s" % (var_types.dest_type, var_types.src2_type)
+        return var_types.src1_type
+
+    def get_ref_opcode(self):
+        """Return the name of the reference code in the softrvv library."""
+        return self.op_code[:-1] if self.is_unsigned() else self.op_code
+
+    def get_imms(self):
+        """Return a list of valid immediate values for a op code."""
+        if self.op_code in ['vsll', 'vsrl', 'vsra', 'vnsrl', 'vnsra']:
+            # Left and right shift immediates must be [0,31]
+            return np.linspace(0, 31, 8, dtype=np.int32)
         # Immediate values must be [-16, 15]
         return np.linspace(-16, 15, 7, dtype=np.int32)
 
+    def get_np_dest_type(self):
+        """Return numpy type for destination."""
+        if self.force_unsigned:
+            types = self.unsigned_np_types
+        else:
+            types = self.signed_np_types
+        if self.is_widening():
+            return types[self.sew * 2]
+        return types[self.sew]
+
+    def get_np_src1_type(self):
+        """Return numpy type for src1."""
+        if self.force_unsigned:
+            types = self.unsigned_np_types
+        else:
+            types = self.signed_np_types
+        return types[self.sew]
+
+    def get_np_src2_type(self):
+        """Return numpy type for src2."""
+        if self.force_unsigned:
+            types = self.unsigned_np_types
+        else:
+            types = self.signed_np_types
+        if self.is_narrowing():
+            return types[self.sew * 2]
+        return types[self.sew]
+
+    def get_test_inputs(self, n=5, allow_zero=True): # pylint: disable=invalid-name
+        """Return test inputs."""
+        src1_np_type = self.get_np_src1_type()
+        src2_np_type = self.get_np_src2_type()
+        type_info = np.iinfo(src1_np_type)
+        src1_data =  np.random.randint(
+            type_info.min, type_info.max, n).astype(src1_np_type)
+        rs1 = self.get_np_src1_type()(np.random.randint(
+            type_info.min, type_info.max))
+        src2_np_type = self.get_np_src2_type()
+        type_info = np.iinfo(src2_np_type)
+        src2_data = np.random.randint(
+            type_info.min, type_info.max, n).astype(src2_np_type)
+        if not allow_zero:
+            src2_data[src2_data==0] = 1
+            rs1 = 1 if rs1 == 0 else rs1
+        return src2_data, src1_data, rs1
+
+def cast_to_unsigned(arr):
+    """Cast a signed array to an unsigned array."""
+    udtypes = {np.int8:np.uint8,
+               np.int16:np.uint16,
+               np.int32:np.uint32,
+               np.int64:np.uint64}
+    if not arr.dtype.type in udtypes.keys():
+        raise TypeError
+    return arr.astype(udtypes[arr.dtype.type])
+
+def to_carr_str(arr):
+    """Simple function for turn array into comma separated list."""
+    return ", ".join(("%s" % x for x in arr))
diff --git a/softrvv/include/softrvv.h b/softrvv/include/softrvv.h
index 189934b..e3a245a 100644
--- a/softrvv/include/softrvv.h
+++ b/softrvv/include/softrvv.h
@@ -10,6 +10,7 @@
 #include "softrvv_vmax.h"
 #include "softrvv_vmin.h"
 #include "softrvv_vmul_vmulh.h"
+#include "softrvv_vnsrl.h"
 #include "softrvv_vor.h"
 #include "softrvv_vrem.h"
 #include "softrvv_vsext_vzext.h"
diff --git a/softrvv/include/softrvv_vnsrl.h b/softrvv/include/softrvv_vnsrl.h
new file mode 100644
index 0000000..58563cd
--- /dev/null
+++ b/softrvv/include/softrvv_vnsrl.h
@@ -0,0 +1,36 @@
+#ifndef SOFTRVV_VNSRL_H
+#define SOFTRVV_VNSRL_H
+
+#include <stddef.h>
+
+#include <type_traits>
+
+namespace softrvv {
+
+template <typename T1, typename T2>
+void vnsrl_vx(T1 *dest, T2 *src1, const T1 *src2, int32_t avl) {
+  // Only low lg2(SEW*2) bits are used for shift
+  static_assert(sizeof(T1) * 2 == sizeof(T2));
+  const T1 low_bits_mask = sizeof(T2) * 8 - 1;
+  const T1 shift = *src2 & low_bits_mask;
+  for (int32_t idx = 0; idx < avl; idx++) {
+    dest[idx] = static_cast<T1>(
+        static_cast<typename std::make_unsigned<T2>::type>(src1[idx]) >> shift);
+  }
+}
+
+template <typename T1, typename T2>
+void vnsrl_vv(T1 *dest, T2 *src1, T1 *src2, int32_t avl) {
+  // // Only low lg2(SEW*2) bits are used for shift
+  static_assert(sizeof(T1) * 2 == sizeof(T2));
+  const T1 low_bits_mask = sizeof(T2) * 8 - 1;
+  for (int32_t idx = 0; idx < avl; idx++) {
+    dest[idx] = static_cast<T1>(
+        static_cast<typename std::make_unsigned<T2>::type>(src1[idx])
+        >> (src2[idx] & low_bits_mask));
+  }
+}
+
+}  // namespace softrvv
+
+#endif  // SOFTRVV_VNSRL_H
diff --git a/softrvv/tests/CMakeLists.txt b/softrvv/tests/CMakeLists.txt
index 6f846d5..ac55038 100644
--- a/softrvv/tests/CMakeLists.txt
+++ b/softrvv/tests/CMakeLists.txt
@@ -188,6 +188,15 @@
    -Xlinker --defsym=__itcm_length__=128K
 )
 
+softrvv_vec_cc_generated_test(
+  NAME
+    vnsrl
+  TEMPLATE
+    softrvv_vnsrl_test.tpl.cpp
+  LINKOPTS
+   -Xlinker --defsym=__itcm_length__=128K
+)
+
 vec_cc_test(
   NAME
     softrvv_vmax
diff --git a/softrvv/tests/templates/opivv_opivx_test.tpl.cpp b/softrvv/tests/templates/opivv_opivx_test.tpl.cpp
index c91eb95..2a21200 100644
--- a/softrvv/tests/templates/opivv_opivx_test.tpl.cpp
+++ b/softrvv/tests/templates/opivv_opivx_test.tpl.cpp
@@ -1,25 +1,18 @@
-<%def name="test_opivv_opivx(dtype, op, src1, src2, rs1, ref_vv, ref_vx)">
-<%
-
-def to_carr_str(arr):
-    return ", ".join(("%s" % x for x in arr))
-
-src1 = to_carr_str(src1)
-src2 = to_carr_str(src2)
-ref_vv = to_carr_str(ref_vv)
-ref_vx = to_carr_str(ref_vx)
+<%!
+import vec_test_helpers
 %>
-namespace softrvv_${op}_test {
+
+<%def name="test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)">
+<%
+src1 = vec_test_helpers.to_carr_str(src1)
+src2 = vec_test_helpers.to_carr_str(src2)
+ref_vv = vec_test_helpers.to_carr_str(ref_vv)
+ref_vx = vec_test_helpers.to_carr_str(ref_vx)
+%>
+namespace softrvv_${template_helper.op_code}_test {
 namespace {
 
-${dtype} src1[] = {${src1}};
-${dtype} src2[] = {${src2}};
-${dtype} rs1 = ${rs1};
-const ${dtype} kAVL = sizeof(src1)/sizeof(src1[0]);
-${dtype} dest[kAVL];
-
-${dtype} ref_vv[] = {${ref_vv}};
-${dtype} ref_vx[] = {${ref_vx}};
+${insert_variable_init(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
 
 template <typename T>
 void assert_vec_elem_eq(int avl, void *test_vector_1, void *test_vector_2) {
@@ -29,22 +22,43 @@
     ASSERT_EQ(ptr_vec_1[idx], ptr_vec_2[idx]);
   }
 }
-
-class SoftRvv${op.capitalize()}Test : public ::testing::Test {
+class SoftRvv${template_helper.op_code.capitalize()}Test : public ::testing::Test {
  protected:
   void SetUp() override { memset(dest, 0, sizeof(dest)); }
 };
 
-TEST_F(SoftRvv${op.capitalize()}Test, VV) {
-  softrvv::${op}_vv<${dtype}>(dest, src1, src2, kAVL);
-  assert_vec_elem_eq<${dtype}>(kAVL, dest, ref_vv);
-}
-
-TEST_F(SoftRvv${op.capitalize()}Test, VX) {
-  softrvv::${op}_vx<${dtype}>(dest, src1, &rs1, kAVL);
-  assert_vec_elem_eq<${dtype}>(kAVL, dest, ref_vx);
-}
+${insert_test(template_helper)}
 
 }  // namespace
 }  // namespace softrvv_${op}_test
 </%def>
+
+<%def name="insert_variable_init(template_helper, src2, src1, rs1, ref_vv, ref_vx)">
+<%
+  var_types = template_helper.get_var_types()
+%>\
+${var_types.src1_type} src1[] = {${src1}};
+${var_types.src2_type} src2[] = {${src2}};
+${var_types.imm_type} rs1 = ${rs1};
+const int kAVL = sizeof(src1)/sizeof(src1[0]);
+${var_types.dest_type} dest[kAVL];
+
+${var_types.dest_type} ref_vv[] = {${ref_vv}};
+${var_types.dest_type} ref_vx[] = {${ref_vx}};
+</%def>\
+
+<%def name="insert_test(template_helper)">
+<%
+var_types = template_helper.get_var_types()
+datatypes = template_helper.get_softrvv_template_data_type()
+%>\
+TEST_F(SoftRvv${template_helper.op_code.capitalize()}Test, VV) {
+  softrvv::${template_helper.op_code}_vv<${datatypes}>(dest, src2, src1, kAVL);
+  assert_vec_elem_eq<${var_types.dest_type}>(kAVL, dest, ref_vv);
+}
+
+TEST_F(SoftRvv${template_helper.op_code.capitalize()}Test, VX) {
+  softrvv::${template_helper.op_code}_vx<${datatypes}>(dest, src2, &rs1, kAVL);
+  assert_vec_elem_eq<${var_types.dest_type}>(kAVL, dest, ref_vx);
+}
+</%def>\
diff --git a/softrvv/tests/templates/softrvv_vadd_test.tpl.cpp b/softrvv/tests/templates/softrvv_vadd_test.tpl.cpp
index a5a6910..12db2ad 100644
--- a/softrvv/tests/templates/softrvv_vadd_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vadd_test.tpl.cpp
@@ -2,8 +2,10 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-ref_vv = src1 + src2
-ref_vx = src1 + rs1
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+ref_vv = src2 + src1
+ref_vx = src2 + rs1
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vdiv_test.tpl.cpp b/softrvv/tests/templates/softrvv_vdiv_test.tpl.cpp
index 0c2bdd3..3e2ad01 100644
--- a/softrvv/tests/templates/softrvv_vdiv_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vdiv_test.tpl.cpp
@@ -2,8 +2,10 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5, allow_zero=False)
-ref_vv = np.divide(src1, src2).astype(np.int32)
-ref_vx = np.divide(src1, rs1).astype(np.int32)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5, allow_zero=False)
+ref_vv = np.divide(src2, src1).astype(np.int32)
+ref_vx = np.divide(src2, rs1).astype(np.int32)
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vmul_test.tpl.cpp b/softrvv/tests/templates/softrvv_vmul_test.tpl.cpp
index 8b47598..bd7caf7 100644
--- a/softrvv/tests/templates/softrvv_vmul_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vmul_test.tpl.cpp
@@ -2,8 +2,10 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-ref_vv = src1 * src2
-ref_vx = src1 * rs1
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+ref_vv = src2 * src1
+ref_vx = src2 * rs1
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vmulh_test.tpl.cpp b/softrvv/tests/templates/softrvv_vmulh_test.tpl.cpp
index 5a77585..60accea 100644
--- a/softrvv/tests/templates/softrvv_vmulh_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vmulh_test.tpl.cpp
@@ -2,10 +2,12 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
 ref_vv = np.right_shift(
-    src1.astype(np.int64) * src2.astype(np.int64), 32).astype(np.int32)
+    src2.astype(np.int64) * src1.astype(np.int64), 32).astype(np.int32)
 ref_vx = np.right_shift(
-    src1.astype(np.int64) * rs1, 32).astype(np.int32)
+    src2.astype(np.int64) * rs1, 32).astype(np.int32)
 %>
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vnsrl_test.tpl.cpp b/softrvv/tests/templates/softrvv_vnsrl_test.tpl.cpp
new file mode 100644
index 0000000..e7c07fc
--- /dev/null
+++ b/softrvv/tests/templates/softrvv_vnsrl_test.tpl.cpp
@@ -0,0 +1,16 @@
+<%inherit file="base.tpl.cpp"/>\
+<%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
+<%
+import numpy as np
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 16)
+N = 5
+src2, src1, rs1 = template_helper.get_test_inputs(n=N)
+src2_type = template_helper.get_np_src2_type()
+tmp_src1 = src1 & (src2_type(0).itemsize * 8 -1)
+dest_type = template_helper.get_np_dest_type()
+ref_vv = np.right_shift(vec_test_helpers.cast_to_unsigned(src2), tmp_src1).astype(dest_type)
+tmp_rs1 = rs1 & (src2_type(0).itemsize * 8 - 1)
+ref_vx = np.right_shift(vec_test_helpers.cast_to_unsigned(src2), tmp_rs1).astype(dest_type)
+%>\
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vrem_test.tpl.cpp b/softrvv/tests/templates/softrvv_vrem_test.tpl.cpp
index 33c42b4..79f6e62 100644
--- a/softrvv/tests/templates/softrvv_vrem_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vrem_test.tpl.cpp
@@ -2,8 +2,12 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.uint32, N=5, allow_zero=False)
-ref_vv = np.mod(src1,src2).astype(np.uint32)
-ref_vx = np.mod(src1, rs1).astype(np.uint32)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+template_helper.force_unsigned = True
+src2, src1, rs1 = template_helper.get_test_inputs(n=5, allow_zero=False)
+dest_type = template_helper.get_np_dest_type()
+ref_vv = np.mod(src2,src1).astype(dest_type)
+ref_vx = np.mod(src2, rs1).astype(dest_type)
 %>\
-${tests.test_opivv_opivx("uint32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vsll_test.tpl.cpp b/softrvv/tests/templates/softrvv_vsll_test.tpl.cpp
index 5c6b102..262d7b2 100644
--- a/softrvv/tests/templates/softrvv_vsll_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vsll_test.tpl.cpp
@@ -2,10 +2,13 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-tmp_src2 = src2 & (np.int32(0).itemsize * 8 -1)
-ref_vv = np.left_shift(src1, tmp_src2)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+template_helper.force_unsigned = True
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+tmp_src1 = src1 & (np.int32(0).itemsize * 8 -1)
+ref_vv = np.left_shift(src2, tmp_src1)
 tmp_rs1 = rs1 & (np.int32(0).itemsize * 8 - 1)
-ref_vx = np.left_shift(src1, tmp_rs1)
+ref_vx = np.left_shift(src2, tmp_rs1)
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vsra_test.tpl.cpp b/softrvv/tests/templates/softrvv_vsra_test.tpl.cpp
index 45ef61d..ebcee5f 100644
--- a/softrvv/tests/templates/softrvv_vsra_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vsra_test.tpl.cpp
@@ -2,10 +2,14 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-tmp_src2 = src2 & (np.int32(0).itemsize * 8 -1)
-ref_vv = np.right_shift(src1, tmp_src2)
-tmp_rs1 = rs1 & (np.int32(0).itemsize * 8 - 1)
-ref_vx = np.right_shift(src1, tmp_rs1)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+template_helper.force_unsigned = True
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+src2_type = template_helper.get_np_src2_type()
+tmp_src1 = src1 & (src2_type(0).itemsize * 8 -1)
+ref_vv = np.right_shift(src2, tmp_src1)
+tmp_rs1 = rs1 & (src2_type(0).itemsize * 8 - 1)
+ref_vx = np.right_shift(src2, tmp_rs1)
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vsrl_test.tpl.cpp b/softrvv/tests/templates/softrvv_vsrl_test.tpl.cpp
index 0a3f95f..f858108 100644
--- a/softrvv/tests/templates/softrvv_vsrl_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vsrl_test.tpl.cpp
@@ -2,10 +2,14 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-tmp_src2 = src2 & (np.int32(0).itemsize * 8 -1)
-ref_vv = np.right_shift(src1.astype(np.uint32), tmp_src2).astype(np.int32)
-tmp_rs1 = rs1 & (np.int32(0).itemsize * 8 - 1)
-ref_vx = np.right_shift(src1.astype(np.uint32), tmp_rs1).astype(np.int32)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+src1_type = template_helper.get_np_src1_type()
+tmp_src1 = src1 & (src1_type(0).itemsize * 8 -1)
+dest_type = template_helper.get_np_dest_type()
+ref_vv = np.right_shift(vec_test_helpers.cast_to_unsigned(src2), tmp_src1).astype(dest_type)
+tmp_rs1 = rs1 & (src1_type(0).itemsize * 8 - 1)
+ref_vx = np.right_shift(vec_test_helpers.cast_to_unsigned(src2), tmp_rs1).astype(dest_type)
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vsub_test.tpl.cpp b/softrvv/tests/templates/softrvv_vsub_test.tpl.cpp
index 9facf2a..0663f4b 100644
--- a/softrvv/tests/templates/softrvv_vsub_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vsub_test.tpl.cpp
@@ -2,8 +2,10 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-ref_vv = src1 - src2
-ref_vx = src1 - rs1
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+ref_vv = src2 - src1
+ref_vx = src2 - rs1
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/softrvv/tests/templates/softrvv_vxor_test.tpl.cpp b/softrvv/tests/templates/softrvv_vxor_test.tpl.cpp
index e87343b..7808a44 100644
--- a/softrvv/tests/templates/softrvv_vxor_test.tpl.cpp
+++ b/softrvv/tests/templates/softrvv_vxor_test.tpl.cpp
@@ -2,8 +2,10 @@
 <%namespace name="tests" file="opivv_opivx_test.tpl.cpp"/>
 <%
 import numpy as np
-src1, src2, rs1 = parent.module.get_test_inputs(np.int32, N=5)
-ref_vv = np.bitwise_xor(src1, src2)
-ref_vx = np.bitwise_xor(src1, rs1)
+import vec_test_helpers
+template_helper = vec_test_helpers.VecTemplateHelper(op, 32)
+src2, src1, rs1 = template_helper.get_test_inputs(n=5)
+ref_vv = np.bitwise_xor(src2, src1)
+ref_vx = np.bitwise_xor(src2, rs1)
 %>\
-${tests.test_opivv_opivx("int32_t", op, src1, src2, rs1, ref_vv, ref_vx)}
+${tests.test_opivv_opivx(template_helper, src2, src1, rs1, ref_vv, ref_vx)}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 4a8ed75..e1de24d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -246,6 +246,15 @@
    -Xlinker --defsym=__itcm_length__=192K
 )
 
+vec_cc_generated_test(
+  NAME
+    vnsrl
+  TEMPLATE
+    opivv_opivx_opivi_test.tpl.cpp
+  LINKOPTS
+   -Xlinker --defsym=__itcm_length__=192K
+)
+
 vec_cc_test(
   NAME
     vsetvl_test
diff --git a/tests/templates/base_opivi_test.tpl.cpp b/tests/templates/base_opivi_test.tpl.cpp
index a3df734..64aa5e0 100644
--- a/tests/templates/base_opivi_test.tpl.cpp
+++ b/tests/templates/base_opivi_test.tpl.cpp
@@ -1,3 +1,6 @@
+<%!
+import vec_test_helpers
+%>\
 <%def name="test_opivi(op_code)">
 namespace ${op_code}_vi_test {
 namespace {
@@ -14,55 +17,18 @@
   void TearDown() override { zero_vector_registers(); }
 };
 <%
-import vec_test_helpers
-sews = vec_test_helpers.get_sews(op_code)
-lmuls = vec_test_helpers.get_lmuls(op_code)
-imms = vec_test_helpers.get_imms(op_code)
+template_helper = vec_test_helpers.VecTemplateHelper(op_code)
+sews = template_helper.get_sews()
+lmuls = template_helper.get_lmuls()
+imms = template_helper.get_imms()
 %>\
-% for test_val in imms:
+% for imm in imms:
 % for sew in sews:
 % for lmul in lmuls:
 <%
-var_type = "int%d_t" % sew
+template_helper.sew = 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);
-  }
-}
+${insert_vi_test(template_helper, lmul, imm)}
 %endfor
 %endfor
 %endfor
@@ -71,3 +37,72 @@
 }  // namespace ${op_code}_vi_test
 </%def>
 
+<%def name="insert_vi_test(template_helper, lmul, imm)">
+<%
+# Initialize the variables for a given test config
+op_code = template_helper.op_code
+sew = template_helper.sew
+ref_opcode = template_helper.get_ref_opcode()
+dest_sew, src2_sew, _, __ = template_helper.get_sew_sizes()
+datatypes = template_helper.get_softrvv_template_data_type()
+var_types = template_helper.get_var_types()
+mnemonic = template_helper.get_mnemonic(
+  vec_test_helpers.VecTemplateHelper.OperandType.IMMEDIATE)
+is_narrowing = template_helper.is_narrowing()
+is_widening = template_helper.is_widening()
+%>\
+TEST_F(${op_code.capitalize()}Test, ${mnemonic.lower().replace(".","_")}${sew}m${lmul}simm5_${("%s" % imm).replace('-','n')}) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+    const ${var_types.imm_type} test_val = ${imm};
+% if is_narrowing:
+    /* When a narrowing instruction is used sew matches dest size */
+    vector_test_setup<${var_types.src2_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1});
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector});
+% elif is_widening:
+    /* When a widening instruction is used sew matches src2 size */
+    vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector});
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.src2_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1});
+% else:
+    /* For non narrowing instructions all vectors have same type*/
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector, src_vector_1});
+% endif
+    if (avl > vlmax) {
+      continue;
+    }
+    ${var_types.src2_type} *ptr_vec_1 = reinterpret_cast<${var_types.src2_type} *>(src_vector_1);
+    ${var_types.dest_type} *ptr_dest_vec = reinterpret_cast<${var_types.dest_type} *>(dest_vector);
+    ${var_types.dest_type} *ptr_ref_dest_vec = reinterpret_cast<${var_types.dest_type} *>(ref_dest_vector);
+    // set up values to test up to index of the AVL
+    fill_random_vector<${var_types.src2_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<${datatypes}>(ptr_ref_dest_vec, ptr_vec_1, &test_val, avl);
+
+    // Load vector registers
+    __asm__ volatile("vle${src2_sew}.v v8, (%0)" : : "r"(ptr_vec_1));
+
+    // Run target instruction
+    __asm__ volatile("${mnemonic} v24, v8, %[SIMM5]" ::[SIMM5] "n"(test_val));
+
+    // Store result vector register
+    __asm__ volatile("vse${dest_sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
+
+    // Check vector elements
+    assert_vec_elem_eq<${var_types.dest_type}>(vlmax, dest_vector, ref_dest_vector);
+  }
+}
+</%def>
diff --git a/tests/templates/base_opivv_test.tpl.cpp b/tests/templates/base_opivv_test.tpl.cpp
index 35b92b8..a7aba2f 100644
--- a/tests/templates/base_opivv_test.tpl.cpp
+++ b/tests/templates/base_opivv_test.tpl.cpp
@@ -1,3 +1,6 @@
+<%!
+import vec_test_helpers
+%>\
 <%def name="test_opivv(op_code)">
 namespace ${op_code}_vv_test {
 namespace {
@@ -15,63 +18,16 @@
   void TearDown() override { zero_vector_registers(); }
 };
 <%
-import vec_test_helpers
-sews = vec_test_helpers.get_sews(op_code)
-lmuls = vec_test_helpers.get_lmuls(op_code)
+template_helper = vec_test_helpers.VecTemplateHelper(op_code)
+sews = template_helper.get_sews()
+lmuls = template_helper.get_lmuls()
 %>\
 % for sew in sews:
 % for lmul in lmuls:
 <%
-dest_type = vec_test_helpers.get_dest_type(op_code, sew)
-src_type = vec_test_helpers.get_src_type(op_code, sew)
-ref_opcode = vec_test_helpers.get_ref_opcode(op_code)
-widening = vec_test_helpers.is_widening(op_code)
+template_helper.sew = 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<${src_type}>(
-        VLMUL::LMUL_M${lmul}, avl,
-        {src_vector_1, src_vector_2, dest_vector, ref_dest_vector});
-    if (avl > vlmax) {
-      continue;
-    }
-    ${src_type} *ptr_vec_1 = reinterpret_cast<${src_type} *>(src_vector_1);
-    ${src_type} *ptr_vec_2 = reinterpret_cast<${src_type} *>(src_vector_2);
-    ${dest_type} *ptr_dest_vec = reinterpret_cast<${dest_type} *>(dest_vector);
-    ${dest_type} *ptr_ref_dest_vec = reinterpret_cast<${dest_type} *>(ref_dest_vector);
-
-    // set up values to test up to index of the AVL
-    fill_random_vector<${src_type}>(ptr_vec_1, avl);
-    fill_random_vector<${src_type}>(ptr_vec_2, avl);
-    memset(dest_vector, 0, MAXVL_BYTES);
-    memset(ref_dest_vector, 0, MAXVL_BYTES);
-
-    // Generate reference vector
-% if widening:
-    softrvv::${ref_opcode}_vv<${dest_type}, ${src_type}>(ptr_ref_dest_vec, ptr_vec_2, ptr_vec_1, avl);
-%else:
-    softrvv::${ref_opcode}_vv<${dest_type}>(ptr_ref_dest_vec, ptr_vec_2, ptr_vec_1, avl);
-% endif
-    // 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
-% if widening:
-    __asm__ volatile("vse${sew*2}.v v24, (%0)" : : "r"(ptr_dest_vec));
-% else:
-    __asm__ volatile("vse${sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
-% endif
-    // Check vector elements
-    assert_vec_elem_eq<${dest_type}>(vlmax, dest_vector, ref_dest_vector);
-  }
-}
+${insert_vv_test(template_helper, lmul)}
 %endfor
 %endfor
 
@@ -79,3 +35,81 @@
 }  // namespace ${op_code}_vv_test
 </%def>
 
+<%def name="insert_vv_test(template_helper, lmul)">
+<%
+# Initialize the variables for a given test config
+op_code = template_helper.op_code
+sew = template_helper.sew
+ref_opcode = template_helper.get_ref_opcode()
+dest_sew, src2_sew, src1_sew, _ =  template_helper.get_sew_sizes()
+datatypes = template_helper.get_softrvv_template_data_type()
+var_types = template_helper.get_var_types()
+mnemonic = template_helper.get_mnemonic(
+  vec_test_helpers.VecTemplateHelper.OperandType.VECTOR)
+is_narrowing = template_helper.is_narrowing()
+is_widening = template_helper.is_widening()
+%>\
+TEST_F(${op_code.capitalize()}Test, ${mnemonic.lower().replace(".","_")}${sew}m${lmul}) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+% if is_narrowing:
+    vector_test_setup<${var_types.src2_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_2});
+    vector_test_setup<${var_types.src1_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1});
+    /* When a narrowing instruction is used sew matches dest size */
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector});
+% elif is_widening:
+    /* When a widening instruction is used sew matches src2 size */
+    vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector});
+    vector_test_setup<${var_types.src1_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_1});
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.src2_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_2});
+% else:
+    /* For non narrowing instructions all vectors have same type*/
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector, src_vector_1});
+% endif
+
+    if (avl > vlmax) {
+      continue;
+    }
+    ${var_types.src1_type} *ptr_vec_1 = reinterpret_cast<${var_types.src1_type} *>(src_vector_1);
+    ${var_types.src2_type} *ptr_vec_2 = reinterpret_cast<${var_types.src2_type} *>(src_vector_2);
+    ${var_types.dest_type} *ptr_dest_vec = reinterpret_cast<${var_types.dest_type} *>(dest_vector);
+    ${var_types.dest_type} *ptr_ref_dest_vec = reinterpret_cast<${var_types.dest_type} *>(ref_dest_vector);
+
+    // set up values to test up to index of the AVL
+    fill_random_vector<${var_types.src1_type}>(ptr_vec_1, avl);
+    fill_random_vector<${var_types.src2_type}>(ptr_vec_2, avl);
+    memset(dest_vector, 0, MAXVL_BYTES);
+    memset(ref_dest_vector, 0, MAXVL_BYTES);
+
+    // Generate reference vector
+    softrvv::${ref_opcode}_vv<${datatypes}>(ptr_ref_dest_vec, ptr_vec_2, ptr_vec_1, avl);
+    // Load vector registers
+    __asm__ volatile("vle${src1_sew}.v v8, (%0)" : : "r"(ptr_vec_1));
+    __asm__ volatile("vle${src2_sew}.v v16, (%0)" : : "r"(ptr_vec_2));
+
+    // Run target instruction
+    __asm__ volatile("${mnemonic} v24, v16, v8");
+
+    // Store result vector register
+    __asm__ volatile("vse${dest_sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
+    // Check vector elements
+    assert_vec_elem_eq<${var_types.dest_type}>(vlmax, dest_vector, ref_dest_vector);
+  }
+}
+</%def>
diff --git a/tests/templates/base_opivx_test.tpl.cpp b/tests/templates/base_opivx_test.tpl.cpp
index 8feaeb4..0800a34 100644
--- a/tests/templates/base_opivx_test.tpl.cpp
+++ b/tests/templates/base_opivx_test.tpl.cpp
@@ -1,10 +1,13 @@
+<%!
+import vec_test_helpers
+%>\
 <%def name="test_opivx(op_code)">
 namespace ${op_code}_vx_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];
 
@@ -14,60 +17,16 @@
   void TearDown() override { zero_vector_registers(); }
 };
 <%
-import vec_test_helpers
-sews = vec_test_helpers.get_sews(op_code)
-lmuls = vec_test_helpers.get_lmuls(op_code)
+template_helper = vec_test_helpers.VecTemplateHelper(op_code)
+sews = template_helper.get_sews()
+lmuls = template_helper.get_lmuls()
 %>\
 % for sew in sews:
 % for lmul in lmuls:
 <%
-dest_type = vec_test_helpers.get_dest_type(op_code, sew)
-src_type = vec_test_helpers.get_src_type(op_code, sew)
-ref_opcode = vec_test_helpers.get_ref_opcode(op_code)
-widening = vec_test_helpers.is_widening(op_code)
+template_helper.sew = 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<${src_type}>(
-        VLMUL::LMUL_M${lmul}, avl,
-        {src_vector_1, dest_vector, ref_dest_vector});
-    if (avl > vlmax) {
-      continue;
-    }
-    ${src_type} *ptr_vec_1 = reinterpret_cast<${src_type} *>(src_vector_1);
-    ${src_type} test_val = static_cast<${src_type}>(rand());
-    ${dest_type} *ptr_dest_vec = reinterpret_cast<${dest_type} *>(dest_vector);
-    ${dest_type} *ptr_ref_dest_vec = reinterpret_cast<${dest_type} *>(ref_dest_vector);
-    // set up values to test up to index of the AVL
-    fill_random_vector<${src_type}>(ptr_vec_1, avl);
-    memset(dest_vector, 0, MAXVL_BYTES);
-    memset(ref_dest_vector, 0, MAXVL_BYTES);
-
-    // Generate reference vector
-% if widening:
-    softrvv::${ref_opcode}_vx<${dest_type}, ${src_type}>(ptr_ref_dest_vec, ptr_vec_1, &test_val, avl);
-%else:
-    softrvv::${ref_opcode}_vx<${dest_type}>(ptr_ref_dest_vec, ptr_vec_1, &test_val, avl);
-% endif
-    // 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
-% if widening:
-    __asm__ volatile("vse${sew*2}.v v24, (%0)" : : "r"(ptr_dest_vec));
-% else:
-    __asm__ volatile("vse${sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
-% endif
-    // Check vector elements
-    assert_vec_elem_eq<${dest_type}>(vlmax, dest_vector, ref_dest_vector);
-  }
-}
+${insert_vx_test(template_helper, lmul)}
 %endfor
 %endfor
 
@@ -75,3 +34,72 @@
 }  // namespace ${op_code}_vx_test
 </%def>
 
+<%def name="insert_vx_test(template_helper, lmul)">
+<%
+# Initialize the variables for a given test config
+op_code = template_helper.op_code
+sew = template_helper.sew
+ref_opcode = template_helper.get_ref_opcode()
+dest_sew, src2_sew, _, __ = template_helper.get_sew_sizes()
+datatypes = template_helper.get_softrvv_template_data_type()
+var_types = template_helper.get_var_types()
+mnemonic = template_helper.get_mnemonic(
+  vec_test_helpers.VecTemplateHelper.OperandType.SCALAR)
+is_narrowing = template_helper.is_narrowing()
+is_widening = template_helper.is_widening()
+%>\
+TEST_F(${op_code.capitalize()}Test, ${mnemonic.lower().replace(".","_")}${sew}m${lmul}) {
+  for (int i = 0; i < AVL_COUNT; i++) {
+    int32_t avl = AVLS[i];
+    int vlmax;
+    int vl;
+% if is_narrowing:
+    /* When a narrowing instruction is used sew matches dest size */
+    vector_test_setup<${var_types.src2_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_2});
+    /* SEW is set to size of dest type */
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector});
+% elif is_widening:
+    /* When a widening instruction is used sew matches src2 size */
+    vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector});
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.src2_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {src_vector_2});
+% else:
+    /* For non narrowing instructions all vectors have same type*/
+    std::tie(vlmax, vl) = vector_test_setup<${var_types.dest_type}>(
+        VLMUL::LMUL_M${lmul}, avl,
+        {dest_vector, ref_dest_vector, src_vector_2});
+% endif
+    if (avl > vlmax) {
+      continue;
+    }
+    ${var_types.src2_type} *ptr_vec_2 = reinterpret_cast<${var_types.src2_type} *>(src_vector_2);
+    ${var_types.imm_type} test_val = static_cast<${var_types.imm_type}>(rand());
+    ${var_types.dest_type} *ptr_dest_vec = reinterpret_cast<${var_types.dest_type} *>(dest_vector);
+    ${var_types.dest_type} *ptr_ref_dest_vec = reinterpret_cast<${var_types.dest_type} *>(ref_dest_vector);
+    // set up values to test up to index of the AVL
+    fill_random_vector<${var_types.src2_type}>(ptr_vec_2, avl);
+    memset(dest_vector, 0, MAXVL_BYTES);
+    memset(ref_dest_vector, 0, MAXVL_BYTES);
+
+    // Generate reference vector
+    softrvv::${ref_opcode}_vx<${datatypes}>(ptr_ref_dest_vec, ptr_vec_2, &test_val, avl);
+    // Load vector registers
+    __asm__ volatile("vle${src2_sew}.v v8, (%0)" : : "r"(ptr_vec_2));
+
+    // Run target instruction
+    __asm__ volatile("${mnemonic} v24, v8, %[RS1]" ::[RS1] "r"(test_val));
+
+    // Store result vector register
+    __asm__ volatile("vse${dest_sew}.v v24, (%0)" : : "r"(ptr_dest_vec));
+    // Check vector elements
+    assert_vec_elem_eq<${var_types.dest_type}>(vlmax, dest_vector, ref_dest_vector);
+  }
+}
+</%def>