Vector extension test with floating point values 1. Vector extension tests with floating point values 2. Rebase all unnecessary commits (See below) 3. Reflects several reviews 4. Clean up codes (by rearranging header files / following style guides) reflects reviews Change-Id: I7b24a230bc57e966a65aecb4f0f0f7c391f8279b Revert "reflects reviews" Revert submission 11560 Reason for revert: Revert this and all related CLs to review the changes again. I will create a new CL by cherry-picking the CL that contains the up-to-date version. Reverted Changes: Id487b5bd4:1. Modify template files for vector tests with flo... I13979c48c:Merge branch 'master' of https://spacebeaker.googl... I7b24a230b:reflects reviews Change-Id: I76c2cae9629c14f97fb7f3e1517be77c7811765b Revert "1. Modify template files for vector tests with floating ..." Revert submission 11560 Reason for revert: Revert this and all related CLs to review the changes again. I will create a new CL by cherry-picking the CL that contains the up-to-date version. Reverted Changes: Id487b5bd4:1. Modify template files for vector tests with flo... I13979c48c:Merge branch 'master' of https://spacebeaker.googl... I7b24a230b:reflects reviews Change-Id: I22e593ff379b2467ac6d9dd48e0415cf87f720c5 Vector extension test with floating point values 1. Modify template files for vector tests with floating point numbers 2. Add two test cases using the modified template for softrvv operations 3. Add manual test case for fadd (tests/vector_vfadd_test.cpp) Change-Id: I12198793053fa1a9cfacea0725cf458d3db6e280
diff --git a/scripts/vec_test_helpers/__init__.py b/scripts/vec_test_helpers/__init__.py index 87f457e..2268bd7 100644 --- a/scripts/vec_test_helpers/__init__.py +++ b/scripts/vec_test_helpers/__init__.py
@@ -14,6 +14,7 @@ VECTOR = enum.auto() SCALAR = enum.auto() IMMEDIATE = enum.auto() + FLOATSCALAR = enum.auto() class OperandWidth(enum.Enum): """RISC-V V operand width type option.""" @@ -37,6 +38,11 @@ OperandWidth.WIDENING: "vi", OperandWidth.NARROWING: "wi", }, + OperandType.FLOATSCALAR : { + OperandWidth.STANDARD: "vf", + OperandWidth.WIDENING: "vf", + OperandWidth.NARROWING: "wf", + }, } """Helper class for providing params for use in templates""" @@ -52,12 +58,14 @@ 8:np.uint8, 16:np.uint16, 32:np.uint32} + self.float_np_types = { + 32:np.float32} @property def op_code(self): """Return the op_code.""" if self._op_code is None: - raise ValueError("SEW was not set.") + raise ValueError("OP CODE was not set.") return self._op_code @op_code.setter @@ -65,6 +73,10 @@ """Set the op_code""" self._op_code = value + def is_floating(self): + """check if a particular op_code is a floating type.""" + return self.op_code[1] == 'f' + @property def sew(self): """Return the selected element width.""" @@ -75,74 +87,100 @@ @sew.setter def sew(self, value): """Set the selected element width.""" + if self.is_floating(): + if not (value == 32): + raise ValueError("Invalid SEW") 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.""" + if self.is_floating(): + raise ValueError("Widening is not supported with this template for floating point values") return self.op_code[1] == 'w' def is_narrowing(self): """Check if a particular op_code is a narrowing type.""" + if self.is_floating(): + raise ValueError("Narrowing is not supported with this template for floating point values") return self.op_code[1] == 'n' def is_destination_mask_register(self): """Check if a particular op_code has a mask output.""" + if self.is_floating(): + return False int_comparison_ops = ('vmseq', 'vmsne', 'vmsltu', 'vmsleu', 'vmsle', 'vmsgtu', 'vmsgt') return self.op_code in int_comparison_ops def is_unsigned(self): """Check if a particular op_code is a unsigned type.""" + if self.is_floating(): + raise ValueError("Invalid unsigned type op code") return self.op_code[-1] == 'u' def get_sews(self): """Given an op_code return a list of valid element widths.""" + if self.is_floating(): + return [32] + if self.is_widening() or self.is_narrowing(): return [8, 16] return [8, 16, 32] def get_sew_sizes(self): - """Return size of types.""" + """Return size of types. + imm is not used for floating point op codes. + """ dest_sew = self.sew src2_sew = self.sew src1_sew = self.sew imm_sew = self.sew - if self.is_narrowing(): + if not self.is_floating() and self.is_narrowing(): src2_sew = self.sew * 2 - elif self.is_widening(): + elif not self.is_floating() and 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.""" + """Return types for an op_code and element width. + imm_type won't be used for floating point values. + """ 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) + if self.is_floating(): + var_types = VarTypes("float", "float", "float", "float") + else: + 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 + if self.is_floating(): + if operand_type == self.OperandType.SCALAR: + operand_type = self.OperandType.FLOATSCALAR + else: + 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] + if not self.is_floating(): + if self.is_widening() or self.is_narrowing(): + return [1, 2, 4] return [1, 2, 4, 8] @staticmethod @@ -154,16 +192,21 @@ 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) + if not self.is_floating(): + 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.""" + if self.is_floating(): + return self.op_code 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.is_floating(): + raise ValueError("imm is not available.") 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) @@ -172,6 +215,8 @@ def get_np_dest_type(self): """Return numpy type for destination.""" + if self.is_floating(): + return self.float_np_types[self.sew] if self.force_unsigned: types = self.unsigned_np_types else: @@ -182,6 +227,8 @@ def get_np_src1_type(self): """Return numpy type for src1.""" + if self.is_floating(): + return self.float_np_types[self.sew] if self.force_unsigned: types = self.unsigned_np_types else: @@ -190,6 +237,8 @@ def get_np_src2_type(self): """Return numpy type for src2.""" + if self.is_floating(): + return self.float_np_types[self.sew] if self.force_unsigned: types = self.unsigned_np_types else: @@ -202,12 +251,28 @@ """Return test inputs.""" src1_np_type = self.get_np_src1_type() src2_np_type = self.get_np_src2_type() + src2_np_type = self.get_np_src2_type() + # Return test inputs for floating points. + if self.is_floating(): + type_info = np.finfo(src1_np_type) + src1_data = np.random.uniform( + type_info.min, type_info.max, n).astype(src1_np_type) + rs1 = self.get_np_src1_type()(np.random.uniform( + type_info.min, type_info.max)) + type_info = np.finfo(src2_np_type) + src2_data = np.random.uniform( + type_info.min, type_info.max, n).astype(src2_np_type) + if not allow_zero: + src2_data[src2_data==0] = 1 + rs1 = 1.0 if rs1 == 0.0 else rs1 + return src2_data, src1_data, rs1 + + # Return test inputs for integers. 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) @@ -217,12 +282,14 @@ return src2_data, src1_data, rs1 def pack_dest_mask(self, values): - """Pack values into a single destination register.""" - dest_type = self.get_np_dest_type() - return np.packbits(dest_type(values), bitorder='little') + """Pack values into a single destination register.""" + dest_type = self.get_np_dest_type() + return np.packbits(dest_type(values), bitorder='little') def cast_to_unsigned(arr): - """Cast a signed array to an unsigned array.""" + """Cast a signed array to an unsigned array. + This should not be called with floating point values. + """ udtypes = {np.int8:np.uint8, np.int16:np.uint16, np.int32:np.uint32,
diff --git a/softrvv/include/softrvv.h b/softrvv/include/softrvv.h index d82e7a0..18c2c21 100644 --- a/softrvv/include/softrvv.h +++ b/softrvv/include/softrvv.h
@@ -2,12 +2,15 @@ #define SOFTRVV_H #include <stddef.h> +#include <stdint.h> #include "encoding.h" #include "softrvv_internal.h" #include "softrvv_vadd.h" #include "softrvv_vand.h" #include "softrvv_vdiv.h" +#include "softrvv_vfsub.h" +#include "softrvv_vfadd.h" #include "softrvv_vmax.h" #include "softrvv_vmin.h" #include "softrvv_vmseq.h"
diff --git a/softrvv/include/softrvv_vfadd.h b/softrvv/include/softrvv_vfadd.h new file mode 100644 index 0000000..0a6633d --- /dev/null +++ b/softrvv/include/softrvv_vfadd.h
@@ -0,0 +1,22 @@ +#ifndef SOFTRVV_VFADD_H +#define SOFTRVV_VFADD_H + +#include <stddef.h> + +namespace softrvv { + +void vfadd_vf(float *dest, float *src1, const float *src2, int32_t avl) { + for (int32_t idx = 0; idx < avl; idx++) { + dest[idx] = src1[idx] + *src2; + } +} + +void vfadd_vv(float *dest, float *src1, float *src2, int32_t avl) { + for (int32_t idx = 0; idx < avl; idx++) { + dest[idx] = src1[idx] + src2[idx]; + } +} + +} // namespace softrvv + +#endif // SOFTRVV_VFADD_H
diff --git a/softrvv/include/softrvv_vfsub.h b/softrvv/include/softrvv_vfsub.h new file mode 100644 index 0000000..aff1f52 --- /dev/null +++ b/softrvv/include/softrvv_vfsub.h
@@ -0,0 +1,28 @@ +#ifndef SOFTRVV_VFSUB_H +#define SOFTRVV_VFSUB_H + +#include <stddef.h> + +namespace softrvv { + +void vfsub_vf(float *dest, float *src1, const float *src2, int32_t avl) { + for (int idx = 0; idx < avl; idx++) { + dest[idx] = src1[idx] - *src2; + } +} + +void vfsub_vv(float *dest, float *src1, float *src2, int32_t avl) { + for (int32_t idx = 0; idx < avl; idx++) { + dest[idx] = src1[idx] - src2[idx]; + } +} + +void vfrsub_vx(float *dest, float *src1, const float *src2, int32_t avl) { + for (int32_t idx = 0; idx < avl; idx++) { + dest[idx] = *src2 - src1[idx]; + } +} + +} // namespace softrvv + +#endif // SOFTRVV_VFSUB_H
diff --git a/softrvv/tests/templates/opivf_test.tpl.cpp b/softrvv/tests/templates/opivf_test.tpl.cpp new file mode 100644 index 0000000..a32bc0d --- /dev/null +++ b/softrvv/tests/templates/opivf_test.tpl.cpp
@@ -0,0 +1,52 @@ +<%! +import vec_test_helpers +%>\ +<%def name="test_opivf(template_helper, src2, rs1, ref_vf)"> +<% +src2 = vec_test_helpers.to_carr_str(src2) +ref_vf = vec_test_helpers.to_carr_str(ref_vf) +%>\ +namespace softrvv_${template_helper.op_code}_vf_test { +namespace { +${insert_variable_init(template_helper, src2, rs1, ref_vf)} + +class SoftRvv${template_helper.op_code.capitalize()}Test : public ::testing::Test { + protected: + void SetUp() override { memset(dest, 0, sizeof(dest)); } +}; +${insert_test(template_helper)}\ +} // namespace +} // namespace softrvv_${op}_vf_test\ +</%def>\ + +<%def name="insert_variable_init(template_helper, src2, rs1, ref_vf)"> +<% + var_types = template_helper.get_var_types() +%>\ +${var_types.src2_type} src2[] = {${src2}}; +${var_types.imm_type} rs1 = ${rs1}; +const int kAVL = sizeof(src2)/sizeof(src2[0]); +${var_types.dest_type} dest[kAVL]; + +${var_types.dest_type} ref_vf[kAVL] = {${ref_vf}}; +</%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, VF) { + softrvv::${template_helper.op_code}_vf(dest, src2, &rs1, kAVL);\ +${insert_check(template_helper, var_types.dest_type, "ref_vf")}\ +} +</%def>\ + +<%def name="insert_check(template_helper, dest_type, ref_var)"> +% if template_helper.is_destination_mask_register(): + assert_vec_mask_eq<${dest_type}>(kAVL, dest, ${ref_var}); +% else: + assert_vec_elem_eq<${dest_type}>(kAVL, dest, ${ref_var}); +% endif +</%def>\
diff --git a/softrvv/tests/templates/opivv_test.tpl.cpp b/softrvv/tests/templates/opivv_test.tpl.cpp new file mode 100644 index 0000000..ab54161 --- /dev/null +++ b/softrvv/tests/templates/opivv_test.tpl.cpp
@@ -0,0 +1,57 @@ +<%! +import vec_test_helpers +%>\ +<%def name="test_opivv(template_helper, src2, src1, ref_vv)"> +<% +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) +%>\ +namespace softrvv_${template_helper.op_code}_vv_test { +namespace { +${insert_variable_init(template_helper, src2, src1, ref_vv)} + +class SoftRvv${template_helper.op_code.capitalize()}Test : public ::testing::Test { + protected: + void SetUp() override { memset(dest, 0, sizeof(dest)); } +}; +${insert_test(template_helper)}\ +} // namespace +} // namespace softrvv_${op}_vv_test\ +</%def>\ + +<%def name="insert_variable_init(template_helper, src2, src1, ref_vv)"> +<% + var_types = template_helper.get_var_types() +%>\ +${var_types.src1_type} src1[] = {${src1}}; +${var_types.src2_type} src2[] = {${src2}}; +const int kAVL = sizeof(src1)/sizeof(src1[0]); +${var_types.dest_type} dest[kAVL]; + +${var_types.dest_type} ref_vv[kAVL] = {${ref_vv}}; +</%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) { + % if template_helper.is_floating(): + softrvv::${template_helper.op_code}_vv(dest, src2, src1, kAVL);\ + % else: + softrvv::${template_helper.op_code}_vv<${datatypes}>(dest, src2, src1, kAVL);\ + % endif +${insert_check(template_helper, var_types.dest_type, "ref_vv")}\ +} + +</%def>\ + +<%def name="insert_check(template_helper, dest_type, ref_var)"> +% if template_helper.is_destination_mask_register(): + assert_vec_mask_eq<${dest_type}>(kAVL, dest, ${ref_var}); +% else: + assert_vec_elem_eq<${dest_type}>(kAVL, dest, ${ref_var}); +% endif +</%def>\
diff --git a/softrvv/tests/templates/softrvv_vfadd_test.tpl.cpp b/softrvv/tests/templates/softrvv_vfadd_test.tpl.cpp new file mode 100644 index 0000000..ec8695b --- /dev/null +++ b/softrvv/tests/templates/softrvv_vfadd_test.tpl.cpp
@@ -0,0 +1,14 @@ +<%inherit file="base.tpl.cpp"/>\ +<%namespace name="tests_vv" file="opivv_test.tpl.cpp"/>\ +<%namespace name="tests_vf" file="opivf_test.tpl.cpp"/>\ +<% +import numpy as np +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_vf = src2 + rs1 +%>\ +${tests_vv.test_opivv(template_helper, src2, src1, ref_vv)} +${tests_vf.test_opivf(template_helper, src2, rs1, ref_vf)} +
diff --git a/softrvv/tests/templates/softrvv_vfsub_test.tpl.cpp b/softrvv/tests/templates/softrvv_vfsub_test.tpl.cpp new file mode 100644 index 0000000..f27fd3a --- /dev/null +++ b/softrvv/tests/templates/softrvv_vfsub_test.tpl.cpp
@@ -0,0 +1,13 @@ +<%inherit file="base.tpl.cpp"/>\ +<%namespace name="tests_vv" file="opivv_test.tpl.cpp"/> +<%namespace name="tests_vf" file="opivf_test.tpl.cpp"/> +<% +import numpy as np +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_vf = src2 - rs1 +%>\ +${tests_vv.test_opivv(template_helper, src2, src1, ref_vv)} +${tests_vf.test_opivf(template_helper, src2, rs1, ref_vf)}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2230dd1..d15c4fd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt
@@ -1,3 +1,4 @@ + vec_cc_generated_test( NAME vsub @@ -391,3 +392,12 @@ LINKOPTS -Xlinker --defsym=__itcm_length__=128K ) + +vec_cc_test( + NAME + vector_vfadd_test + SRCS + vector_vfadd_test.cpp + LINKOPTS + -Xlinker --defsym=__itcm_length__=128K +)
diff --git a/tests/vector_vfadd_test.cpp b/tests/vector_vfadd_test.cpp new file mode 100644 index 0000000..208cb91 --- /dev/null +++ b/tests/vector_vfadd_test.cpp
@@ -0,0 +1,67 @@ +#include <softrvv.h> +#include <springbok.h> +#include <stdio.h> + +#include <tuple> + +#include "pw_unit_test/framework.h" +#include "test_v_helpers.h" + +namespace vfadd_vv_test +{ +namespace +{ +using namespace test_v_helpers; + +float src_vector_1[MAXVL_BYTES]; +float src_vector_2[MAXVL_BYTES]; +float dest_vector[MAXVL_BYTES]; +float ref_dest_vector[MAXVL_BYTES]; + +class VfaddTest : public ::testing::Test { + protected: + void SetUp() override { zero_vector_registers(); } + void TearDown() override { zero_vector_registers(); } +}; + +TEST_F(VfaddTest, vfadd_vv32m1) { + 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<int32_t>( + VLMUL::LMUL_M1, avl, + {dest_vector, ref_dest_vector, src_vector_1, src_vector_2}); + + if (avl > vlmax) { + continue; + } + + float *ptr_vec_1 = reinterpret_cast<float *>(src_vector_1); + float *ptr_vec_2 = reinterpret_cast<float *>(src_vector_2); + float *ptr_dest_vec = reinterpret_cast<float *>(dest_vector); + float *ptr_ref_dest_vec = reinterpret_cast<float *>(ref_dest_vector); + + // set up values to test up to index of the AVL + fill_random_vector<float>(ptr_vec_1, avl); + fill_random_vector<float>(ptr_vec_2, avl); + + // Generate reference vector + softrvv::vfadd_vv(ptr_ref_dest_vec, ptr_vec_2, ptr_vec_1, avl); + // Load vector registers + __asm__ volatile("vle32.v v8, (%0)" : : "r"(ptr_vec_1)); + __asm__ volatile("vle32.v v16, (%0)" : : "r"(ptr_vec_2)); + + // Run target instruction + __asm__ volatile("vfadd.vv v24, v16, v8"); + + // Store result vector register + __asm__ volatile("vse32.v v24, (%0)" : : "r"(ptr_dest_vec)); + // Check vector elements + assert_vec_elem_eq<float>(vlmax, dest_vector, ref_dest_vector); + } +} +} // namespace +} // namespace vfadd_vv_test