Internal change PiperOrigin-RevId: 548872652
diff --git a/sim/test/BUILD b/sim/test/BUILD index 735c876..c365fd9 100644 --- a/sim/test/BUILD +++ b/sim/test/BUILD
@@ -107,6 +107,23 @@ ) cc_test( + name = "kelvin_vector_memory_instructions_test", + srcs = ["kelvin_vector_memory_instructions_test.cc"], + copts = [ + "-Werror", + "-Wvla-extension", + ], + deps = [ + ":kelvin_vector_instructions_test_base", + "//sim:kelvin_instructions", + "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + "@com_google_mpact-sim//mpact/sim/generic:instruction", + ], +) + +cc_test( name = "kelvin_log_instructions_test", srcs = ["kelvin_log_instructions_test.cc"], copts = [
diff --git a/sim/test/kelvin_vector_instructions_test.cc b/sim/test/kelvin_vector_instructions_test.cc index 250ffab..f4c4c0b 100644 --- a/sim/test/kelvin_vector_instructions_test.cc +++ b/sim/test/kelvin_vector_instructions_test.cc
@@ -10,7 +10,6 @@ #include <utility> #include <vector> -#include "sim/kelvin_vector_memory_instructions.h" #include "sim/test/kelvin_vector_instructions_test_base.h" #include "googletest/include/gtest/gtest.h" #include "absl/functional/bind_front.h" @@ -18,14 +17,13 @@ #include "absl/strings/string_view.h" #include "mpact/sim/generic/instruction.h" -// This file contains the tests for testing kelvin vector instructions. +// This file contains the tests for testing kelvin vector binary instructions. namespace { using mpact::sim::generic::Instruction; // Semantic functions. -using kelvin::sim::KelvinGetVl; using kelvin::sim::KelvinVAbsd; using kelvin::sim::KelvinVAcc; using kelvin::sim::KelvinVAdd; @@ -45,8 +43,6 @@ using kelvin::sim::KelvinVGt; using kelvin::sim::KelvinVHadd; using kelvin::sim::KelvinVHsub; -using kelvin::sim::KelvinVLd; -using kelvin::sim::KelvinVLdRegWrite; using kelvin::sim::KelvinVLe; using kelvin::sim::KelvinVLt; using kelvin::sim::KelvinVMacc; @@ -78,8 +74,6 @@ using kelvin::sim::KelvinVSra; using kelvin::sim::KelvinVSrans; using kelvin::sim::KelvinVSrl; -using kelvin::sim::KelvinVSt; -using kelvin::sim::KelvinVStQ; using kelvin::sim::KelvinVSub; using kelvin::sim::KelvinVSubs; using kelvin::sim::KelvinVSubsu; @@ -106,23 +100,6 @@ class KelvinVectorInstructionsTest : public kelvin::sim::test::KelvinVectorInstructionsTestBase { public: - template <typename T> - absl::string_view KelvinTestTypeSuffix() { - absl::string_view type_suffix = "Unknown"; - switch (sizeof(T)) { - case 4: - type_suffix = "W"; - break; - case 2: - type_suffix = "H"; - break; - case 1: - type_suffix = "B"; - break; - } - return type_suffix; - } - template <template <typename, typename, typename> class F, typename TD, typename TS1, typename TS2> void KelvinVectorBinaryOpHelper(absl::string_view name) { @@ -477,7 +454,9 @@ KelvinVAbsd<Vs1>(scalar, strip_mine, inst); } }; -TEST_F(KelvinVectorInstructionsTest, VAbsd) { + +// TODO(b/291683818): Check the implementation and re-enable the logic. +TEST_F(KelvinVectorInstructionsTest, DISABLED_VAbsd) { KelvinVectorBinaryOpHelper<VAbsdOp, uint8_t, int8_t, int8_t, uint16_t, int16_t, int16_t, uint32_t, int32_t, int32_t>( "VAbsd"); @@ -1682,391 +1661,4 @@ KelvinShuffleOpHelper<VZipOp, int8_t, int16_t, int32_t>("VZip", kWidenDst); } -class KelvinVectorInstructionsMemoryTest : public KelvinVectorInstructionsTest { - public: - template <typename T> - void MemoryLoadStoreOpTestHelper(absl::string_view name, bool has_length, - bool has_stride, bool strip_mine, - bool post_increment, bool x_variant, - bool is_load, bool is_quad) { - InstructionPtr child_instruction( - new Instruction(next_instruction_address_, state_), - [](Instruction *inst) { inst->DecRef(); }); - child_instruction->set_size(4); - auto instruction = CreateInstruction(); - - if (is_load) { - child_instruction->set_semantic_function( - absl::bind_front(&KelvinVLdRegWrite<T>, strip_mine)); - instruction->set_semantic_function( - absl::bind_front(&KelvinVLd<T>, has_length, has_stride, strip_mine)); - instruction->AppendChild(child_instruction.get()); - } else { - if (is_quad) { - instruction->set_semantic_function( - absl::bind_front(&KelvinVStQ<T>, strip_mine)); - } else { - instruction->set_semantic_function(absl::bind_front( - &KelvinVSt<T>, has_length, has_stride, strip_mine)); - } - } - - // Setup source and child instruction operands. - const uint32_t num_ops = strip_mine ? 4 : 1; - if (is_load) { - AppendVectorRegisterOperands( - child_instruction.get(), num_ops, 1 /* src1_widen_factor */, {}, {}, - false /* widen_dst */, {kelvin::sim::test::kVd}); - } else { // Store - AppendVectorRegisterOperands( - instruction.get(), num_ops, 1 /* src1_widen_factor */, - kelvin::sim::test::kVd, {}, false /* widen_dst */, {}); - } - AppendRegisterOperands(instruction.get(), {kelvin::sim::test::kRs1Name}, - {}); - if (!x_variant) { - AppendRegisterOperands(instruction.get(), {kelvin::sim::test::kRs2Name}, - {}); - } - - if (post_increment) { - AppendRegisterOperands(instruction.get(), {}, - {kelvin::sim::test::kRs1Name}); - } - - // x variant can't have length or stride fields. - if (x_variant && (has_length || has_stride)) { - GTEST_FAIL(); - } - - // xx variant can't have no length, no stride, and no post_increment - // encoding - if (!x_variant && !has_length && !has_stride && !post_increment) { - GTEST_FAIL(); - } - - // length and stride fields can't coexist without post_increment - if (has_length && has_stride && !post_increment) { - GTEST_FAIL(); - } - - // Quad store need to have stride specified and no length - if (is_quad && is_load) { - GTEST_FAIL(); - } - if ((is_quad && has_length) || (is_quad && !has_stride)) { - GTEST_FAIL(); - } - const uint32_t vector_length_in_bytes = state_->vector_length() / 8; - const uint32_t vd_size = vector_length_in_bytes / sizeof(T); - const uint32_t len_or_strides[] = {0, 1, vd_size - 1, - vd_size, 2 * vd_size, 4 * vd_size}; - - // Check with different values for length and stride if applicable. - for (int test = 0; - test < (has_length || has_stride ? std::size(len_or_strides) : 1); - test++) { - // Store stride can't be smaller than vd_size - if ((is_quad && len_or_strides[test] < vd_size / 4) || - (!is_load && has_stride && len_or_strides[test] < vd_size)) { - continue; - } - // Set input register values. - SetRegisterValues<uint32_t>( - {{kelvin::sim::test::kRs1Name, kelvin::sim::test::kDataLoadAddress}}); - - if (!x_variant) { - SetRegisterValues<uint32_t>( - {{kelvin::sim::test::kRs2Name, len_or_strides[test]}}); - } - - // Fill vector register(s) with random values. - std::vector<T> vd_value(vector_length_in_bytes / sizeof(T) * num_ops); - auto vd_span = absl::Span<T>(vd_value); - FillArrayWithRandomValues<T>(vd_span); - for (int i = 0; i < num_ops; i++) { - auto vd_name = absl::StrCat("v", kelvin::sim::test::kVd + i); - SetVectorRegisterValues<T>( - {{vd_name, vd_span.subspan(vd_size * i, vd_size)}}); - } - - // Execute instruction. - instruction->Execute(); - - // Compute memory values. For load test it is the expected output; for - // store test it is the actual output. - std::vector<T> memory_values(vd_size * num_ops); - uint32_t addr = kelvin::sim::test::kDataLoadAddress; - uint32_t rs2_value = len_or_strides[test]; - uint32_t count = vd_size * num_ops; - if (has_length) { - count = std::min(count, rs2_value); - } - uint32_t left = count; - for (int op_num = 0; op_num < num_ops; op_num++) { - const int n = std::min(vd_size, left); - if (is_quad) { - const uint32_t quad_size = vd_size / 4; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < quad_size; ++j) { - memory_values[op_num * vd_size + i * quad_size + j] = - GetSavedMemoryValue<T>(addr + - (i * quad_size + j) * sizeof(T)); - } - // Stride increase per quad_size. - addr += rs2_value * sizeof(T); - } - } else { - for (int i = 0; i < vd_size; ++i) { - if (is_load) { - memory_values[op_num * vd_size + i] = - i < n ? GetDefaultMemoryValue<T>(addr + i * sizeof(T)) : 0; - } else { - memory_values[op_num * vd_size + i] = - i < n ? GetSavedMemoryValue<T>(addr + i * sizeof(T)) : 0; - } - } - left -= n; - if (has_stride) { - addr += rs2_value * sizeof(T); - } else { - addr += n * sizeof(T); - } - } - } - - uint32_t expected_rs1_value = kelvin::sim::test::kDataLoadAddress; - if (post_increment && count) { - if (has_length && has_stride) { // .tp - expected_rs1_value += vd_size * sizeof(T); - } else if (!has_length && !has_stride && x_variant) { // .p.x - expected_rs1_value += vd_size * sizeof(T) * num_ops; - } else if (has_length) { // .lp - expected_rs1_value += count * sizeof(T); - } else if (has_stride) { // .sp - const uint32_t quad_scale = is_quad ? 4 : 1; - expected_rs1_value += rs2_value * sizeof(T) * num_ops * quad_scale; - } else { // .p.xx - expected_rs1_value += rs2_value * sizeof(T); - } - } - - // Check result - left = count; - for (int op_num = 0; op_num < num_ops; op_num++) { - auto vreg_num = kelvin::sim::test::kVd + op_num; - auto test_vreg = vreg_[vreg_num]; - auto vreg_span = test_vreg->data_buffer()->Get<T>(); - if (is_load) { - for (int element_index = 0; element_index < vd_size; - element_index++) { - auto vreg_element_index = op_num * vd_size + element_index; - EXPECT_EQ(memory_values[vreg_element_index], - vreg_span[element_index]) - << absl::StrCat(name, "[", vreg_element_index, "] != reg[", - vreg_num, "*", element_index, "]"); - } - } else { // Store - const int n = std::min(vd_size, left); - for (int element_index = 0; - element_index < vd_size && element_index < n; element_index++) { - auto total_element_index = op_num * vd_size + element_index; - EXPECT_EQ(memory_values[total_element_index], - vreg_span[element_index]) - << absl::StrCat(name, " mem at ", total_element_index, - " != vreg[", vreg_num, "][", element_index, - "]"); - } - left -= n; - } - } - - if (post_increment) { - // Check rs1 value. - auto *reg = state_ - ->GetRegister<kelvin::sim::test::RV32Register>( - kelvin::sim::test::kRs1Name) - .first; - EXPECT_EQ(expected_rs1_value, reg->data_buffer()->Get<uint32_t>()[0]) - << absl::StrCat(name, " post incremented rs1 is incorrect."); - } - } - } - - template <typename T> - void MemoryLoadStoreOpTestHelper(absl::string_view name, bool is_load) { - constexpr bool kNoLength = false; - constexpr bool kLength = true; - constexpr bool kNoStride = false; - constexpr bool kStride = true; - constexpr bool kPostIncrement = true; - constexpr bool kXVariant = true; - constexpr bool kNotXVariant = false; - constexpr bool kNotQuad = false; - - const auto name_with_type = absl::StrCat(name, KelvinTestTypeSuffix<T>()); - - for (auto strip_mine : {false, true}) { - for (auto post_increment : {false, true}) { - // .x variants. - auto subname = absl::StrCat(name_with_type, post_increment ? "P" : "", - "X", strip_mine ? "M" : ""); - MemoryLoadStoreOpTestHelper<T>(subname, kNoLength, kNoStride, - strip_mine, post_increment, kXVariant, - is_load, kNotQuad); - } - // .xx variants - for (auto len_stride_post : - {std::tuple(false, false, true), std::tuple(false, true, false), - std::tuple(false, true, true), std::tuple(true, false, false), - std::tuple(true, false, true)}) { - auto has_length = std::get<0>(len_stride_post); - auto has_stride = std::get<1>(len_stride_post); - auto post_increment = std::get<2>(len_stride_post); - auto subname = absl::StrCat(name_with_type, - has_length ? "L" - : has_stride ? "S" - : "", - post_increment ? "P" : "", "XX", - strip_mine ? "M" : ""); - MemoryLoadStoreOpTestHelper<T>(subname, has_length, has_stride, - strip_mine, post_increment, kNotXVariant, - is_load, kNotQuad); - } - - // .tp variants. - auto subname = - absl::StrCat(name_with_type, "TP", "XX", strip_mine ? "M" : ""); - MemoryLoadStoreOpTestHelper<T>(subname, kLength, kStride, strip_mine, - kPostIncrement, kNotXVariant, is_load, - kNotQuad); - } - } - - template <typename T> - void StoreQuadOpTestHelper(absl::string_view name) { - const auto name_with_type = absl::StrCat(name, KelvinTestTypeSuffix<T>()); - constexpr bool kNotLength = false; - constexpr bool kStride = true; - constexpr bool kNotXVariant = false; - constexpr bool kNotLoad = false; - constexpr bool kIsQuad = true; - for (auto strip_mine : {false, true}) { - for (auto post_increment : {false, true}) { - auto subname = - absl::StrCat(name_with_type, "S", post_increment ? "P" : "", "XX", - strip_mine ? "M" : ""); - MemoryLoadStoreOpTestHelper<T>(subname, kNotLength, kStride, strip_mine, - post_increment, kNotXVariant, kNotLoad, - kIsQuad); - } - } - } - - template <typename T1, typename TNext1, typename... TNext> - void MemoryLoadStoreOpTestHelper(absl::string_view name, bool is_load) { - MemoryLoadStoreOpTestHelper<T1>(name, is_load); - MemoryLoadStoreOpTestHelper<TNext1, TNext...>(name, is_load); - } - - template <typename T1, typename TNext1, typename... TNext> - void StoreQuadOpTestHelper(absl::string_view name) { - StoreQuadOpTestHelper<T1>(name); - StoreQuadOpTestHelper<TNext1, TNext...>(name); - } - - protected: - template <typename T> - T GetDefaultMemoryValue(int address) { - T value = 0; - uint8_t *ptr = reinterpret_cast<uint8_t *>(&value); - for (int j = 0; j < sizeof(T); j++) { - ptr[j] = (address + j) & 0xff; - } - return value; - } - - template <typename T> - T GetSavedMemoryValue(int address) { - auto *db = state_->db_factory()->Allocate<T>(1); - memory_->Load(address, db, nullptr, nullptr); - T data = db->template Get<T>(0); - db->DecRef(); - return data; - } -}; - -TEST_F(KelvinVectorInstructionsMemoryTest, VLd) { - MemoryLoadStoreOpTestHelper<int8_t, int16_t, int32_t>("VLd", - /*is_load=*/true); -} - -TEST_F(KelvinVectorInstructionsMemoryTest, VSt) { - MemoryLoadStoreOpTestHelper<int8_t, int16_t, int32_t>("VSt", - /*is_load=*/false); -} - -TEST_F(KelvinVectorInstructionsMemoryTest, VStQ) { - StoreQuadOpTestHelper<int8_t, int16_t, int32_t>("VStQ"); -} - -class KelvinGetVlInstructionTest : public KelvinVectorInstructionsTest { - public: - template <typename T> - void GetVlTestHelper() { - constexpr char kRdName[] = "x8"; - constexpr uint32_t kMaxVlenInBytes = kelvin::sim::kVectorLengthInBits / 8; - auto instruction = CreateInstruction(); - AppendRegisterOperands( - instruction.get(), - {kelvin::sim::test::kRs1Name, kelvin::sim::test::kRs2Name}, {kRdName}); - for (auto strip_mine : {false, true}) { - for (auto is_rs1 : {false, true}) { - for (auto is_rs2 : {false, true}) { - uint32_t rs1_value = RandomValue(); - uint32_t rs2_value = RandomValue(); - SetRegisterValues<uint32_t>({{kelvin::sim::test::kRs1Name, rs1_value}, - {kelvin::sim::test::kRs2Name, rs2_value}, - {kRdName, UINT32_MAX}}); - instruction->set_semantic_function( - absl::bind_front(&KelvinGetVl<T>, strip_mine, is_rs1, is_rs2)); - uint32_t expected_vlen = - kMaxVlenInBytes / sizeof(T) * (strip_mine ? 4 : 1); - if (is_rs1) { - expected_vlen = std::min(expected_vlen, rs1_value); - } - if (is_rs2) { - expected_vlen = std::min(expected_vlen, rs2_value); - } - // Execute instruction. - instruction->Execute(nullptr); - EXPECT_EQ(xreg_[8]->data_buffer()->Get<uint32_t>(0), expected_vlen) - << "Test failed with type " - << (sizeof(T) == 4 ? "W" : (sizeof(T) == 2 ? "H" : "B")) - << ", strip_mine: " << strip_mine << ", rs1_set: " << is_rs1 - << ", rs2_set: " << is_rs2; - } - } - } - } - - template <typename T1, typename TNext1, typename... TNext> - void GetVlTestHelper() { - GetVlTestHelper<T1>(); - GetVlTestHelper<TNext1, TNext...>(); - } - - protected: - // Create a random value in the valid range for the type. - uint32_t RandomValue() { - return absl::Uniform(absl::IntervalClosed, bitgen_, - std::numeric_limits<uint32_t>::lowest(), - std::numeric_limits<uint32_t>::max()); - } -}; - -TEST_F(KelvinGetVlInstructionTest, GetVl) { - GetVlTestHelper<int8_t, int16_t, int32_t>(); -} - } // namespace
diff --git a/sim/test/kelvin_vector_instructions_test_base.h b/sim/test/kelvin_vector_instructions_test_base.h index 9afde0b..7335a28 100644 --- a/sim/test/kelvin_vector_instructions_test_base.h +++ b/sim/test/kelvin_vector_instructions_test_base.h
@@ -76,6 +76,23 @@ } } + template <typename T> + absl::string_view KelvinTestTypeSuffix() { + absl::string_view type_suffix = "Unknown"; + switch (sizeof(T)) { + case 4: + type_suffix = "W"; + break; + case 2: + type_suffix = "H"; + break; + case 1: + type_suffix = "B"; + break; + } + return type_suffix; + } + template <typename Vd, typename Vs1, typename Vs2> static std::pair<Vs1, Vs2> CommonBinaryOpArgsGetter( int num_ops, int op_num, int dest_reg_sub_index, int element_index,
diff --git a/sim/test/kelvin_vector_memory_instructions_test.cc b/sim/test/kelvin_vector_memory_instructions_test.cc new file mode 100644 index 0000000..4b4fc82 --- /dev/null +++ b/sim/test/kelvin_vector_memory_instructions_test.cc
@@ -0,0 +1,420 @@ +#include "sim/kelvin_vector_memory_instructions.h" + +#include <assert.h> + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <utility> +#include <vector> + +#include "sim/test/kelvin_vector_instructions_test_base.h" +#include "googletest/include/gtest/gtest.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "mpact/sim/generic/instruction.h" + +// This file contains the tests for testing kelvin vector memory instructions. + +namespace { + +using mpact::sim::generic::Instruction; + +// Semantic functions. +using kelvin::sim::KelvinGetVl; +using kelvin::sim::KelvinVLd; +using kelvin::sim::KelvinVLdRegWrite; +using kelvin::sim::KelvinVSt; +using kelvin::sim::KelvinVStQ; + +class KelvinVectorMemoryInstructionsTest + : public kelvin::sim::test::KelvinVectorInstructionsTestBase { + public: + template <typename T> + void MemoryLoadStoreOpTestHelper(absl::string_view name, bool has_length, + bool has_stride, bool strip_mine, + bool post_increment, bool x_variant, + bool is_load, bool is_quad) { + InstructionPtr child_instruction( + new Instruction(next_instruction_address_, state_), + [](Instruction *inst) { inst->DecRef(); }); + child_instruction->set_size(4); + auto instruction = CreateInstruction(); + + if (is_load) { + child_instruction->set_semantic_function( + absl::bind_front(&KelvinVLdRegWrite<T>, strip_mine)); + instruction->set_semantic_function( + absl::bind_front(&KelvinVLd<T>, has_length, has_stride, strip_mine)); + instruction->AppendChild(child_instruction.get()); + } else { + if (is_quad) { + instruction->set_semantic_function( + absl::bind_front(&KelvinVStQ<T>, strip_mine)); + } else { + instruction->set_semantic_function(absl::bind_front( + &KelvinVSt<T>, has_length, has_stride, strip_mine)); + } + } + + // Setup source and child instruction operands. + const uint32_t num_ops = strip_mine ? 4 : 1; + if (is_load) { + AppendVectorRegisterOperands( + child_instruction.get(), num_ops, 1 /* src1_widen_factor */, {}, {}, + false /* widen_dst */, {kelvin::sim::test::kVd}); + } else { // Store + AppendVectorRegisterOperands( + instruction.get(), num_ops, 1 /* src1_widen_factor */, + kelvin::sim::test::kVd, {}, false /* widen_dst */, {}); + } + AppendRegisterOperands(instruction.get(), {kelvin::sim::test::kRs1Name}, + {}); + if (!x_variant) { + AppendRegisterOperands(instruction.get(), {kelvin::sim::test::kRs2Name}, + {}); + } + + if (post_increment) { + AppendRegisterOperands(instruction.get(), {}, + {kelvin::sim::test::kRs1Name}); + } + + // x variant can't have length or stride fields. + if (x_variant && (has_length || has_stride)) { + GTEST_FAIL(); + } + + // xx variant can't have no length, no stride, and no post_increment + // encoding + if (!x_variant && !has_length && !has_stride && !post_increment) { + GTEST_FAIL(); + } + + // length and stride fields can't coexist without post_increment + if (has_length && has_stride && !post_increment) { + GTEST_FAIL(); + } + + // Quad store need to have stride specified and no length + if (is_quad && is_load) { + GTEST_FAIL(); + } + if ((is_quad && has_length) || (is_quad && !has_stride)) { + GTEST_FAIL(); + } + const uint32_t vector_length_in_bytes = state_->vector_length() / 8; + const uint32_t vd_size = vector_length_in_bytes / sizeof(T); + const uint32_t len_or_strides[] = {0, 1, vd_size - 1, + vd_size, 2 * vd_size, 4 * vd_size}; + + // Check with different values for length and stride if applicable. + for (int test = 0; + test < (has_length || has_stride ? std::size(len_or_strides) : 1); + test++) { + // Store stride can't be smaller than vd_size + if ((is_quad && len_or_strides[test] < vd_size / 4) || + (!is_load && has_stride && len_or_strides[test] < vd_size)) { + continue; + } + // Set input register values. + SetRegisterValues<uint32_t>( + {{kelvin::sim::test::kRs1Name, kelvin::sim::test::kDataLoadAddress}}); + + if (!x_variant) { + SetRegisterValues<uint32_t>( + {{kelvin::sim::test::kRs2Name, len_or_strides[test]}}); + } + + // Fill vector register(s) with random values. + std::vector<T> vd_value(vector_length_in_bytes / sizeof(T) * num_ops); + auto vd_span = absl::Span<T>(vd_value); + FillArrayWithRandomValues<T>(vd_span); + for (int i = 0; i < num_ops; i++) { + auto vd_name = absl::StrCat("v", kelvin::sim::test::kVd + i); + SetVectorRegisterValues<T>( + {{vd_name, vd_span.subspan(vd_size * i, vd_size)}}); + } + + // Execute instruction. + instruction->Execute(); + + // Compute memory values. For load test it is the expected output; for + // store test it is the actual output. + std::vector<T> memory_values(vd_size * num_ops); + uint32_t addr = kelvin::sim::test::kDataLoadAddress; + uint32_t rs2_value = len_or_strides[test]; + uint32_t count = vd_size * num_ops; + if (has_length) { + count = std::min(count, rs2_value); + } + uint32_t left = count; + for (int op_num = 0; op_num < num_ops; op_num++) { + const int n = std::min(vd_size, left); + if (is_quad) { + const uint32_t quad_size = vd_size / 4; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < quad_size; ++j) { + memory_values[op_num * vd_size + i * quad_size + j] = + GetSavedMemoryValue<T>(addr + + (i * quad_size + j) * sizeof(T)); + } + // Stride increase per quad_size. + addr += rs2_value * sizeof(T); + } + } else { + for (int i = 0; i < vd_size; ++i) { + if (is_load) { + memory_values[op_num * vd_size + i] = + i < n ? GetDefaultMemoryValue<T>(addr + i * sizeof(T)) : 0; + } else { + memory_values[op_num * vd_size + i] = + i < n ? GetSavedMemoryValue<T>(addr + i * sizeof(T)) : 0; + } + } + left -= n; + if (has_stride) { + addr += rs2_value * sizeof(T); + } else { + addr += n * sizeof(T); + } + } + } + + uint32_t expected_rs1_value = kelvin::sim::test::kDataLoadAddress; + if (post_increment && count) { + if (has_length && has_stride) { // .tp + expected_rs1_value += vd_size * sizeof(T); + } else if (!has_length && !has_stride && x_variant) { // .p.x + expected_rs1_value += vd_size * sizeof(T) * num_ops; + } else if (has_length) { // .lp + expected_rs1_value += count * sizeof(T); + } else if (has_stride) { // .sp + const uint32_t quad_scale = is_quad ? 4 : 1; + expected_rs1_value += rs2_value * sizeof(T) * num_ops * quad_scale; + } else { // .p.xx + expected_rs1_value += rs2_value * sizeof(T); + } + } + + // Check result + left = count; + for (int op_num = 0; op_num < num_ops; op_num++) { + auto vreg_num = kelvin::sim::test::kVd + op_num; + auto test_vreg = vreg_[vreg_num]; + auto vreg_span = test_vreg->data_buffer()->Get<T>(); + if (is_load) { + for (int element_index = 0; element_index < vd_size; + element_index++) { + auto vreg_element_index = op_num * vd_size + element_index; + EXPECT_EQ(memory_values[vreg_element_index], + vreg_span[element_index]) + << absl::StrCat(name, "[", vreg_element_index, "] != reg[", + vreg_num, "*", element_index, "]"); + } + } else { // Store + const int n = std::min(vd_size, left); + for (int element_index = 0; + element_index < vd_size && element_index < n; element_index++) { + auto total_element_index = op_num * vd_size + element_index; + EXPECT_EQ(memory_values[total_element_index], + vreg_span[element_index]) + << absl::StrCat(name, " mem at ", total_element_index, + " != vreg[", vreg_num, "][", element_index, + "]"); + } + left -= n; + } + } + + if (post_increment) { + // Check rs1 value. + auto *reg = state_ + ->GetRegister<kelvin::sim::test::RV32Register>( + kelvin::sim::test::kRs1Name) + .first; + EXPECT_EQ(expected_rs1_value, reg->data_buffer()->Get<uint32_t>()[0]) + << absl::StrCat(name, " post incremented rs1 is incorrect."); + } + } + } + + template <typename T> + void MemoryLoadStoreOpTestHelper(absl::string_view name, bool is_load) { + constexpr bool kNoLength = false; + constexpr bool kLength = true; + constexpr bool kNoStride = false; + constexpr bool kStride = true; + constexpr bool kPostIncrement = true; + constexpr bool kXVariant = true; + constexpr bool kNotXVariant = false; + constexpr bool kNotQuad = false; + + const auto name_with_type = absl::StrCat(name, KelvinTestTypeSuffix<T>()); + + for (auto strip_mine : {false, true}) { + for (auto post_increment : {false, true}) { + // .x variants. + auto subname = absl::StrCat(name_with_type, post_increment ? "P" : "", + "X", strip_mine ? "M" : ""); + MemoryLoadStoreOpTestHelper<T>(subname, kNoLength, kNoStride, + strip_mine, post_increment, kXVariant, + is_load, kNotQuad); + } + // .xx variants + for (auto len_stride_post : + {std::tuple(false, false, true), std::tuple(false, true, false), + std::tuple(false, true, true), std::tuple(true, false, false), + std::tuple(true, false, true)}) { + auto has_length = std::get<0>(len_stride_post); + auto has_stride = std::get<1>(len_stride_post); + auto post_increment = std::get<2>(len_stride_post); + auto subname = absl::StrCat(name_with_type, + has_length ? "L" + : has_stride ? "S" + : "", + post_increment ? "P" : "", "XX", + strip_mine ? "M" : ""); + MemoryLoadStoreOpTestHelper<T>(subname, has_length, has_stride, + strip_mine, post_increment, kNotXVariant, + is_load, kNotQuad); + } + + // .tp variants. + auto subname = + absl::StrCat(name_with_type, "TP", "XX", strip_mine ? "M" : ""); + MemoryLoadStoreOpTestHelper<T>(subname, kLength, kStride, strip_mine, + kPostIncrement, kNotXVariant, is_load, + kNotQuad); + } + } + + template <typename T> + void StoreQuadOpTestHelper(absl::string_view name) { + const auto name_with_type = absl::StrCat(name, KelvinTestTypeSuffix<T>()); + constexpr bool kNotLength = false; + constexpr bool kStride = true; + constexpr bool kNotXVariant = false; + constexpr bool kNotLoad = false; + constexpr bool kIsQuad = true; + for (auto strip_mine : {false, true}) { + for (auto post_increment : {false, true}) { + auto subname = + absl::StrCat(name_with_type, "S", post_increment ? "P" : "", "XX", + strip_mine ? "M" : ""); + MemoryLoadStoreOpTestHelper<T>(subname, kNotLength, kStride, strip_mine, + post_increment, kNotXVariant, kNotLoad, + kIsQuad); + } + } + } + + template <typename T1, typename TNext1, typename... TNext> + void MemoryLoadStoreOpTestHelper(absl::string_view name, bool is_load) { + MemoryLoadStoreOpTestHelper<T1>(name, is_load); + MemoryLoadStoreOpTestHelper<TNext1, TNext...>(name, is_load); + } + + template <typename T1, typename TNext1, typename... TNext> + void StoreQuadOpTestHelper(absl::string_view name) { + StoreQuadOpTestHelper<T1>(name); + StoreQuadOpTestHelper<TNext1, TNext...>(name); + } + + protected: + template <typename T> + T GetDefaultMemoryValue(int address) { + T value = 0; + uint8_t *ptr = reinterpret_cast<uint8_t *>(&value); + for (int j = 0; j < sizeof(T); j++) { + ptr[j] = (address + j) & 0xff; + } + return value; + } + + template <typename T> + T GetSavedMemoryValue(int address) { + auto *db = state_->db_factory()->Allocate<T>(1); + memory_->Load(address, db, nullptr, nullptr); + T data = db->template Get<T>(0); + db->DecRef(); + return data; + } +}; + +TEST_F(KelvinVectorMemoryInstructionsTest, VLd) { + MemoryLoadStoreOpTestHelper<int8_t, int16_t, int32_t>("VLd", + /*is_load=*/true); +} + +TEST_F(KelvinVectorMemoryInstructionsTest, VSt) { + MemoryLoadStoreOpTestHelper<int8_t, int16_t, int32_t>("VSt", + /*is_load=*/false); +} + +TEST_F(KelvinVectorMemoryInstructionsTest, VStQ) { + StoreQuadOpTestHelper<int8_t, int16_t, int32_t>("VStQ"); +} + +class KelvinGetVlInstructionTest + : public kelvin::sim::test::KelvinVectorInstructionsTestBase { + public: + template <typename T> + void GetVlTestHelper() { + constexpr char kRdName[] = "x8"; + constexpr uint32_t kMaxVlenInBytes = kelvin::sim::kVectorLengthInBits / 8; + auto instruction = CreateInstruction(); + AppendRegisterOperands( + instruction.get(), + {kelvin::sim::test::kRs1Name, kelvin::sim::test::kRs2Name}, {kRdName}); + for (auto strip_mine : {false, true}) { + for (auto is_rs1 : {false, true}) { + for (auto is_rs2 : {false, true}) { + uint32_t rs1_value = RandomValue(); + uint32_t rs2_value = RandomValue(); + SetRegisterValues<uint32_t>({{kelvin::sim::test::kRs1Name, rs1_value}, + {kelvin::sim::test::kRs2Name, rs2_value}, + {kRdName, UINT32_MAX}}); + instruction->set_semantic_function( + absl::bind_front(&KelvinGetVl<T>, strip_mine, is_rs1, is_rs2)); + uint32_t expected_vlen = + kMaxVlenInBytes / sizeof(T) * (strip_mine ? 4 : 1); + if (is_rs1) { + expected_vlen = std::min(expected_vlen, rs1_value); + } + if (is_rs2) { + expected_vlen = std::min(expected_vlen, rs2_value); + } + // Execute instruction. + instruction->Execute(nullptr); + EXPECT_EQ(xreg_[8]->data_buffer()->Get<uint32_t>(0), expected_vlen) + << "Test failed with type " + << (sizeof(T) == 4 ? "W" : (sizeof(T) == 2 ? "H" : "B")) + << ", strip_mine: " << strip_mine << ", rs1_set: " << is_rs1 + << ", rs2_set: " << is_rs2; + } + } + } + } + + template <typename T1, typename TNext1, typename... TNext> + void GetVlTestHelper() { + GetVlTestHelper<T1>(); + GetVlTestHelper<TNext1, TNext...>(); + } + + protected: + // Create a random value in the valid range for the type. + uint32_t RandomValue() { + return absl::Uniform(absl::IntervalClosed, bitgen_, + std::numeric_limits<uint32_t>::lowest(), + std::numeric_limits<uint32_t>::max()); + } +}; + +TEST_F(KelvinGetVlInstructionTest, GetVl) { + GetVlTestHelper<int8_t, int16_t, int32_t>(); +} + +} // namespace