| // Copyright 2023 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #ifndef MPACT_RISCV_RISCV_TEST_RISCV_VECTOR_INSTRUCTIONS_TEST_BASE_H_ | |
| #define MPACT_RISCV_RISCV_TEST_RISCV_VECTOR_INSTRUCTIONS_TEST_BASE_H_ | |
| #include <algorithm> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <functional> | |
| #include <ios> | |
| #include <limits> | |
| #include <string> | |
| #include <tuple> | |
| #include <vector> | |
| #include "absl/functional/bind_front.h" | |
| #include "absl/random/random.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/span.h" | |
| #include "cheriot/cheriot_register.h" | |
| #include "cheriot/cheriot_state.h" | |
| #include "cheriot/cheriot_vector_state.h" | |
| #include "cheriot/riscv_cheriot_vector_memory_instructions.h" | |
| #include "googlemock/include/gmock/gmock.h" | |
| #include "mpact/sim/generic/data_buffer.h" | |
| #include "mpact/sim/generic/immediate_operand.h" | |
| #include "mpact/sim/generic/instruction.h" | |
| #include "mpact/sim/generic/register.h" | |
| #include "mpact/sim/generic/type_helpers.h" | |
| #include "mpact/sim/util/memory/tagged_flat_demand_memory.h" | |
| #include "riscv//riscv_register.h" | |
| // This file defines commonly used constants in the vector instruction tests | |
| // as well as a base class for the vector instruction test fixtures. This base | |
| // class contains methods that make it more convenient to write vector | |
| // instruction test cases, and provide "harnesses" to test the functionality | |
| // of individual instructions across different lmul values, vstart values, | |
| // and vector length values. | |
| using ::absl::Span; | |
| using ::mpact::sim::cheriot::CheriotRegister; | |
| using ::mpact::sim::cheriot::CheriotState; | |
| using ::mpact::sim::cheriot::CheriotVectorState; | |
| using ::mpact::sim::cheriot::Vsetvl; | |
| using ::mpact::sim::generic::ImmediateOperand; | |
| using ::mpact::sim::generic::Instruction; | |
| using ::mpact::sim::generic::NarrowType; | |
| using ::mpact::sim::generic::RegisterBase; | |
| using ::mpact::sim::generic::SameSignedType; | |
| using ::mpact::sim::generic::WideType; | |
| using ::mpact::sim::riscv::RiscVState; | |
| using ::mpact::sim::riscv::RV32VectorDestinationOperand; | |
| using ::mpact::sim::riscv::RV32VectorSourceOperand; | |
| using ::mpact::sim::riscv::RVFpRegister; | |
| using ::mpact::sim::riscv::RVVectorRegister; | |
| using ::mpact::sim::util::TaggedFlatDemandMemory; | |
| using ::std::tuple; | |
| // Constants used in the tests. | |
| constexpr int kVectorLengthInBits = 512; | |
| constexpr int kVectorLengthInBytes = kVectorLengthInBits / 8; | |
| constexpr uint32_t kInstAddress = 0x1000; | |
| constexpr uint32_t kDataLoadAddress = 0x1'0000; | |
| constexpr uint32_t kDataStoreAddress = 0x2'0000; | |
| constexpr char kRs1Name[] = "c1"; | |
| constexpr int kRs1 = 1; | |
| constexpr char kRs2Name[] = "c2"; | |
| constexpr char kRs3Name[] = "c3"; | |
| constexpr char kRdName[] = "c8"; | |
| constexpr int kRd = 8; | |
| constexpr int kVmask = 1; | |
| constexpr char kVmaskName[] = "v1"; | |
| constexpr int kVd = 8; | |
| constexpr char kVdName[] = "v8"; | |
| constexpr int kVs1 = 16; | |
| constexpr char kVs1Name[] = "v16"; | |
| constexpr int kVs2 = 24; | |
| constexpr char kVs2Name[] = "v24"; | |
| // Setting bits and corresponding values for lmul and sew. | |
| constexpr int kLmulSettings[7] = {0b101, 0b110, 0b111, 0b000, | |
| 0b001, 0b010, 0b011}; | |
| constexpr int kLmul8Values[7] = {1, 2, 4, 8, 16, 32, 64}; | |
| constexpr int kLmulSettingByLogSize[] = {0, 0b101, 0b110, 0b111, | |
| 0b000, 0b001, 0b010, 0b011}; | |
| constexpr int kSewSettings[4] = {0b000, 0b001, 0b010, 0b011}; | |
| constexpr int kSewValues[4] = {1, 2, 4, 8}; | |
| constexpr int kSewSettingsByByteSize[] = {0, 0b000, 0b001, 0, 0b010, | |
| 0, 0, 0, 0b011}; | |
| // Don't need to set every byte, as only the low bits are used for mask values. | |
| constexpr uint8_t kA5Mask[kVectorLengthInBytes] = { | |
| 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, | |
| 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, | |
| 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, | |
| 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, | |
| 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, | |
| 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, | |
| }; | |
| // This is the base class for vector instruction test fixtures. It implements | |
| // generic methods for testing and supporting testing of the RiscV vector | |
| // instructions. | |
| class RiscVCheriotVectorInstructionsTestBase : public testing::Test { | |
| public: | |
| RiscVCheriotVectorInstructionsTestBase() { | |
| memory_ = new TaggedFlatDemandMemory(8); | |
| state_ = new CheriotState("test", memory_); | |
| rv_vector_ = new CheriotVectorState(state_, kVectorLengthInBytes); | |
| instruction_ = new Instruction(kInstAddress, state_); | |
| instruction_->set_size(4); | |
| child_instruction_ = new Instruction(kInstAddress, state_); | |
| child_instruction_->set_size(4); | |
| // Initialize a portion of memory with a known pattern. | |
| auto *db = state_->db_factory()->Allocate(8192); | |
| auto span = db->Get<uint8_t>(); | |
| for (int i = 0; i < 8192; i++) { | |
| span[i] = i & 0xff; | |
| } | |
| memory_->Store(kDataLoadAddress - 4096, db); | |
| db->DecRef(); | |
| for (int i = 1; i < 32; i++) { | |
| creg_[i] = | |
| state_->GetRegister<CheriotRegister>(absl::StrCat("c", i)).first; | |
| } | |
| for (int i = 1; i < 32; i++) { | |
| freg_[i] = state_->GetRegister<RVFpRegister>(absl::StrCat("f", i)).first; | |
| } | |
| for (int i = 1; i < 32; i++) { | |
| vreg_[i] = | |
| state_->GetRegister<RVVectorRegister>(absl::StrCat("v", i)).first; | |
| } | |
| } | |
| ~RiscVCheriotVectorInstructionsTestBase() override { | |
| delete state_; | |
| delete rv_vector_; | |
| instruction_->DecRef(); | |
| child_instruction_->DecRef(); | |
| delete memory_; | |
| } | |
| // Clear the instruction instance and allocate a new one. | |
| void ResetInstruction() { | |
| instruction_->DecRef(); | |
| instruction_ = new Instruction(kInstAddress, state_); | |
| instruction_->set_size(4); | |
| } | |
| // Creates immediate operands with the values from the vector and appends them | |
| // to the given instruction. | |
| template <typename T> | |
| void AppendImmediateOperands(Instruction *inst, | |
| const std::vector<T> &values) { | |
| for (auto value : values) { | |
| auto *src = new ImmediateOperand<T>(value); | |
| inst->AppendSource(src); | |
| } | |
| } | |
| // Creates immediate operands with the values from the vector and appends them | |
| // to the default instruction. | |
| template <typename T> | |
| void AppendImmediateOperands(const std::vector<T> &values) { | |
| AppendImmediateOperands<T>(instruction_, values); | |
| } | |
| // Creates source and destination scalar register operands for the registers | |
| // named in the two vectors and append them to the given instruction. | |
| void AppendRegisterOperands(Instruction *inst, | |
| const std::vector<std::string> &sources, | |
| const std::vector<std::string> &destinations) { | |
| for (auto ®_name : sources) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; | |
| inst->AppendSource(reg->CreateSourceOperand()); | |
| } | |
| for (auto ®_name : destinations) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; | |
| inst->AppendDestination(reg->CreateDestinationOperand(0)); | |
| } | |
| } | |
| // Creates source and destination scalar register operands for the registers | |
| // named in the two vectors and append them to the default instruction. | |
| void AppendRegisterOperands(const std::vector<std::string> &sources, | |
| const std::vector<std::string> &destinations) { | |
| AppendRegisterOperands(instruction_, sources, destinations); | |
| } | |
| // Returns the value of the named vector register. | |
| template <typename T> | |
| T GetRegisterValue(absl::string_view vreg_name) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(vreg_name).first; | |
| return reg->data_buffer()->Get<T>(); | |
| } | |
| // named register and sets it to the corresponding value. | |
| template <typename T, typename RegisterType = CheriotRegister> | |
| void SetRegisterValues( | |
| const std::vector<tuple<std::string, const T>> &values) { | |
| for (auto &[reg_name, value] : values) { | |
| auto *reg = state_->GetRegister<RegisterType>(reg_name).first; | |
| auto *db = | |
| state_->db_factory()->Allocate<typename RegisterType::ValueType>(1); | |
| db->template Set<T>(0, value); | |
| reg->SetDataBuffer(db); | |
| db->DecRef(); | |
| } | |
| } | |
| // Creates source and destination scalar register operands for the registers | |
| // named in the two vectors and append them to the given instruction. | |
| void AppendVectorRegisterOperands(Instruction *inst, | |
| const std::vector<int> &sources, | |
| const std::vector<int> &destinations) { | |
| for (auto ®_no : sources) { | |
| std::vector<RegisterBase *> reg_vec; | |
| for (int i = 0; (i < 8) && (i + reg_no < 32); i++) { | |
| std::string reg_name = absl::StrCat("v", i + reg_no); | |
| reg_vec.push_back( | |
| state_->GetRegister<RVVectorRegister>(reg_name).first); | |
| } | |
| auto *op = new RV32VectorSourceOperand( | |
| absl::Span<RegisterBase *>(reg_vec), absl::StrCat("v", reg_no)); | |
| inst->AppendSource(op); | |
| } | |
| for (auto ®_no : destinations) { | |
| std::vector<RegisterBase *> reg_vec; | |
| for (int i = 0; (i < 8) && (i + reg_no < 32); i++) { | |
| std::string reg_name = absl::StrCat("v", i + reg_no); | |
| reg_vec.push_back( | |
| state_->GetRegister<RVVectorRegister>(reg_name).first); | |
| } | |
| auto *op = new RV32VectorDestinationOperand( | |
| absl::Span<RegisterBase *>(reg_vec), 0, absl::StrCat("v", reg_no)); | |
| inst->AppendDestination(op); | |
| } | |
| } | |
| // Creates source and destination scalar register operands for the registers | |
| // named in the two vectors and append them to the default instruction. | |
| void AppendVectorRegisterOperands(const std::vector<int> &sources, | |
| const std::vector<int> &destinations) { | |
| AppendVectorRegisterOperands(instruction_, sources, destinations); | |
| } | |
| // Returns the value of the named vector register. | |
| template <typename T> | |
| T GetVectorRegisterValue(absl::string_view reg_name) { | |
| auto *reg = state_->GetRegister<RVVectorRegister>(reg_name).first; | |
| return reg->data_buffer()->Get<T>(0); | |
| } | |
| // Set a vector register value. Takes a vector of tuples of register names and | |
| // spans of values, fetches each register and sets it to the corresponding | |
| // value. | |
| template <typename T> | |
| void SetVectorRegisterValues( | |
| const std::vector<tuple<std::string, Span<const T>>> &values) { | |
| for (auto &[vreg_name, span] : values) { | |
| auto *vreg = state_->GetRegister<RVVectorRegister>(vreg_name).first; | |
| auto *db = state_->db_factory()->MakeCopyOf(vreg->data_buffer()); | |
| db->template Set<T>(span); | |
| vreg->SetDataBuffer(db); | |
| db->DecRef(); | |
| } | |
| } | |
| // Initializes the semantic function of the instruction object. | |
| void SetSemanticFunction(Instruction *inst, | |
| Instruction::SemanticFunction fcn) { | |
| inst->set_semantic_function(fcn); | |
| } | |
| // Initializes the semantic function for the default instruction. | |
| void SetSemanticFunction(Instruction::SemanticFunction fcn) { | |
| instruction_->set_semantic_function(fcn); | |
| } | |
| // Sets the default child instruction as the child of the default instruction. | |
| void SetChildInstruction() { instruction_->AppendChild(child_instruction_); } | |
| // Initializes the semantic function for the default child instruction. | |
| void SetChildSemanticFunction(Instruction::SemanticFunction fcn) { | |
| child_instruction_->set_semantic_function(fcn); | |
| } | |
| // Configure the vector unit according to the vtype and vlen values. | |
| void ConfigureVectorUnit(uint32_t vtype, uint32_t vlen) { | |
| Instruction *inst = new Instruction(state_); | |
| AppendImmediateOperands<uint32_t>(inst, {vlen, vtype}); | |
| SetSemanticFunction(inst, absl::bind_front(&Vsetvl, true, false)); | |
| inst->Execute(nullptr); | |
| inst->DecRef(); | |
| } | |
| // Clear count registers in the register group, starting at start. | |
| void ClearVectorRegisterGroup(int start, int count) { | |
| for (int reg = start; (reg < start + count) && (reg < 32); reg++) { | |
| memset(vreg_[reg]->data_buffer()->raw_ptr(), 0, kVectorLengthInBytes); | |
| } | |
| } | |
| // Create a random value in the valid range for the type. | |
| template <typename T> | |
| T RandomValue() { | |
| return absl::Uniform(absl::IntervalClosed, bitgen_, | |
| std::numeric_limits<T>::lowest(), | |
| std::numeric_limits<T>::max()); | |
| } | |
| // Fill the span with random values. | |
| template <typename T> | |
| void FillArrayWithRandomValues(absl::Span<T> span) { | |
| for (auto &val : span) { | |
| val = RandomValue<T>(); | |
| } | |
| } | |
| // Helper function for testing unary vector-vector instructions. | |
| template <typename Vd, typename Vs2> | |
| void UnaryOpTestHelperV(absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vd) && byte_sew != sizeof(Vs2)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vd: " << sizeof(Vd) | |
| << " Vs2: " << sizeof(Vs2); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| // Input values for 8 registers. | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| AppendVectorRegisterOperands({kVs2, kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| } | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vd = lmul8 * sizeof(Vd) / byte_sew; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| ASSERT_TRUE(vlen > vstart); | |
| // Configure vector unit for different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| ClearVectorRegisterGroup(kVd, 8); | |
| inst->Execute(); | |
| if (lmul8_vd < 1 || lmul8_vd > 64) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()) | |
| << "lmul8: vd: " << lmul8_vd; | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| if (lmul8_vs2 < 1 || lmul8_vs2 > 64) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()) | |
| << "lmul8: vs2: " << lmul8_vs2; | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| EXPECT_EQ(rv_vector_->vstart(), 0); | |
| int count = 0; | |
| for (int reg = kVd; reg < kVd + 8; reg++) { | |
| for (int i = 0; i < kVectorLengthInBytes / sizeof(Vd); i++) { | |
| int mask_index = count >> 3; | |
| int mask_offset = count & 0b111; | |
| bool mask_value = | |
| ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| if ((count >= vstart) && mask_value && (count < num_values)) { | |
| EXPECT_EQ(operation(vs2_value[count]), | |
| vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << absl::StrCat(name, "[", count, "] != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ")"); | |
| } else { | |
| EXPECT_EQ(0, vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << absl::StrCat(name, " 0 != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ")"); | |
| } | |
| count++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Helper function for testing vector-vector instructions that use the value | |
| // of the mask bit. | |
| template <typename Vd, typename Vs2, typename Vs1> | |
| void BinaryOpWithMaskTestHelperVV( | |
| absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Vs1, bool)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vd) && byte_sew != sizeof(Vs2) && | |
| byte_sew != sizeof(Vs1)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vd: " << sizeof(Vd) | |
| << " Vs2: " << sizeof(Vs2) << " Vs1: " << sizeof(Vs1); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1); | |
| // Input values for 8 registers. | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| Vs1 vs1_value[vs1_size * 8]; | |
| auto vs1_span = Span<Vs1>(vs1_value); | |
| AppendVectorRegisterOperands({kVs2, kVs1, kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| FillArrayWithRandomValues<Vs1>(vs1_span); | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs1_name = absl::StrCat("v", kVs1 + i); | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| SetVectorRegisterValues<Vs1>( | |
| {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}}); | |
| } | |
| // Iterate across the different lmul values. | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| // Try different vstart values. | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vd = lmul8 * sizeof(Vd) / byte_sew; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int lmul8_vs1 = lmul8 * sizeof(Vs1) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| // Configure vector unit for different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| ClearVectorRegisterGroup(kVd, 8); | |
| inst->Execute(); | |
| if ((std::min(std::min(lmul8_vs2, lmul8_vs1), lmul8_vd) < 1) || | |
| (std::max(std::max(lmul8_vs2, lmul8_vs1), lmul8_vd) > 64)) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()); | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| EXPECT_EQ(rv_vector_->vstart(), 0); | |
| int count = 0; | |
| for (int reg = kVd; reg < kVd + 8; reg++) { | |
| for (int i = 0; i < kVectorLengthInBytes / sizeof(Vd); i++) { | |
| int mask_index = count >> 3; | |
| int mask_offset = count & 0b111; | |
| bool mask_value = | |
| ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| if ((count >= vstart) && (count < num_values)) { | |
| EXPECT_EQ( | |
| operation(vs2_value[count], vs1_value[count], mask_value), | |
| vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << std::hex << (int64_t)vs2_value[count] << ", " | |
| << (int64_t)vs1_value[count] << " " << std::dec | |
| << (int64_t)vs2_value[count] << ", " | |
| << (int64_t)vs1_value[count] << " " | |
| << absl::StrCat(name, "[", count, "] != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ") vstart(", vstart, | |
| ")"); | |
| } else { | |
| EXPECT_EQ(0, vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << absl::StrCat(name, " 0 != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ") vstart(", vstart, | |
| ")"); | |
| } | |
| count++; | |
| } | |
| } | |
| if (HasFailure()) return; | |
| } | |
| } | |
| } | |
| } | |
| // Helper function for testing vector-vector instructions that do not | |
| // use the value of the mask bit. | |
| template <typename Vd, typename Vs2, typename Vs1> | |
| void BinaryOpTestHelperVV(absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Vs1)> operation) { | |
| BinaryOpWithMaskTestHelperVV<Vd, Vs2, Vs1>( | |
| name, sew, inst, [operation](Vs2 vs2, Vs1 vs1, bool mask_value) -> Vd { | |
| if (mask_value) { | |
| return operation(vs2, vs1); | |
| } | |
| return 0; | |
| }); | |
| } | |
| // Helper function for testing vector-scalar/immediate instructions that use | |
| // the value of the mask bit. | |
| template <typename Vd, typename Vs2, typename Rs1> | |
| void BinaryOpWithMaskTestHelperVX( | |
| absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Rs1, bool)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vd) && byte_sew != sizeof(Vs2) && | |
| byte_sew != sizeof(Rs1)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vd: " << sizeof(Vd) | |
| << " Vs2: " << sizeof(Vs2) << " Rs1: " << sizeof(Rs1); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| // Input values for 8 registers. | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| AppendVectorRegisterOperands({kVs2}, {}); | |
| AppendRegisterOperands({kRs1Name}, {}); | |
| AppendVectorRegisterOperands({kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| } | |
| // Iterate across the different lmul values. | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| // Try different vstart values. | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vd = lmul8 * sizeof(Vd) / byte_sew; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| // Set vstart, but leave vstart at 0 at least once. | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| ASSERT_TRUE(vlen > vstart); | |
| // Configure vector unit for the different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| ClearVectorRegisterGroup(kVd, 8); | |
| // Generate a new rs1 value. | |
| CheriotRegister::ValueType rs1_reg_value = | |
| RandomValue<CheriotRegister::ValueType>(); | |
| SetRegisterValues<CheriotRegister::ValueType>( | |
| {{kRs1Name, rs1_reg_value}}); | |
| // Cast the value to the appropriate width, sign-extending if need | |
| // be. | |
| Rs1 rs1_value = static_cast<Rs1>( | |
| static_cast<typename SameSignedType<CheriotRegister::ValueType, | |
| Rs1>::type>(rs1_reg_value)); | |
| inst->Execute(); | |
| if ((std::min(lmul8_vs2, lmul8_vd) < 1) || | |
| (std::max(lmul8_vs2, lmul8_vd) > 64)) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()); | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| EXPECT_EQ(rv_vector_->vstart(), 0); | |
| int count = 0; | |
| for (int reg = kVd; reg < kVd + 8; reg++) { | |
| for (int i = 0; i < kVectorLengthInBytes / sizeof(Vd); i++) { | |
| int mask_index = count >> 3; | |
| int mask_offset = count & 0b111; | |
| bool mask_value = | |
| ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| // Compare elements that are between vstart and vlen for which | |
| // the mask is true. | |
| if ((count >= vstart) && (count < num_values)) { | |
| Vd expected_value = operation( | |
| vs2_value[count], static_cast<Rs1>(rs1_value), mask_value); | |
| Vd inst_value = vreg_[reg]->data_buffer()->Get<Vd>(i); | |
| EXPECT_EQ(expected_value, inst_value) << absl::StrCat( | |
| name, " [", count, "] != reg[", reg, "][", i, "] lmul8(", | |
| lmul8, ") op(", absl::Hex(vs2_value[count]), ", ", | |
| absl::Hex(static_cast<Rs1>(rs1_value)), | |
| ") vreg: ", absl::Hex(inst_value)); | |
| } else { | |
| // The others should be zero. | |
| EXPECT_EQ(0, vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << absl::StrCat(name, " 0 != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ")"); | |
| } | |
| count++; | |
| } | |
| } | |
| if (HasFailure()) return; | |
| } | |
| } | |
| } | |
| } | |
| // Templated helper function that tests vector-scalar instructions that do | |
| // not use the value of the mask bit. | |
| template <typename Vd, typename Vs2, typename Vs1> | |
| void BinaryOpTestHelperVX(absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Vs1)> operation) { | |
| BinaryOpWithMaskTestHelperVX<Vd, Vs2, Vs1>( | |
| name, sew, inst, [operation](Vs2 vs2, Vs1 vs1, bool mask_value) -> Vd { | |
| if (mask_value) { | |
| return operation(vs2, vs1); | |
| } | |
| return 0; | |
| }); | |
| } | |
| // Helper function for testing vector-vector instructions that use the value | |
| // of the mask bit. | |
| template <typename Vd, typename Vs2, typename Vs1> | |
| void TernaryOpWithMaskTestHelperVV( | |
| absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Vs1, Vd, bool)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vd) && byte_sew != sizeof(Vs2) && | |
| byte_sew != sizeof(Vs1)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vd: " << sizeof(Vd) | |
| << " Vs2: " << sizeof(Vs2) << " Vs1: " << sizeof(Vs1); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vd_size = kVectorLengthInBytes / sizeof(Vd); | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1); | |
| // Input values for 8 registers. | |
| Vd vd_value[vd_size * 8]; | |
| auto vd_span = Span<Vd>(vd_value); | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| Vs1 vs1_value[vs1_size * 8]; | |
| auto vs1_span = Span<Vs1>(vs1_value); | |
| AppendVectorRegisterOperands({kVs2, kVs1, kVd, kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vd>(vd_span); | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| FillArrayWithRandomValues<Vs1>(vs1_span); | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs1_name = absl::StrCat("v", kVs1 + i); | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| SetVectorRegisterValues<Vs1>( | |
| {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}}); | |
| } | |
| // Iterate across the different lmul values. | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| // Try different vstart values. | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vd = lmul8 * sizeof(Vd) / byte_sew; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int lmul8_vs1 = lmul8 * sizeof(Vs1) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| // Configure vector unit for different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| // Reset Vd values, since the previous instruction execution | |
| // overwrites them. | |
| for (int i = 0; i < 8; i++) { | |
| auto vd_name = absl::StrCat("v", kVd + i); | |
| SetVectorRegisterValues<Vd>( | |
| {{vd_name, vd_span.subspan(vd_size * i, vd_size)}}); | |
| } | |
| inst->Execute(); | |
| if ((std::min(std::min(lmul8_vs2, lmul8_vs1), lmul8_vd) < 1) || | |
| (std::max(std::max(lmul8_vs2, lmul8_vs1), lmul8_vd) > 64)) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()); | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| int count = 0; | |
| int reg_offset = count * byte_sew / kVectorLengthInBytes; | |
| for (int reg = kVd + reg_offset; reg < kVd + 8; reg++) { | |
| for (int i = 0; i < kVectorLengthInBytes / sizeof(Vd); i++) { | |
| int mask_index = count >> 3; | |
| int mask_offset = count & 0b111; | |
| bool mask_value = | |
| ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| if ((count >= vstart) && (count < num_values)) { | |
| EXPECT_EQ(operation(vs2_value[count], vs1_value[count], | |
| vd_value[count], mask_value), | |
| vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << "mask: " << mask_value << " (" << std::hex | |
| << (int64_t)vs2_value[count] << ", " | |
| << (int64_t)vs1_value[count] << ") (" << std::dec | |
| << (int64_t)vs2_value[count] << ", " | |
| << (int64_t)vs1_value[count] << ", " | |
| << (int64_t)vd_value[count] << ") " | |
| << absl::StrCat(name, "[", count, "] != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ") vstart(", vstart, | |
| ")"); | |
| } else { | |
| EXPECT_EQ(vd_value[count], | |
| vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << absl::StrCat(name, " 0 != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ") vstart(", vstart, | |
| ")"); | |
| } | |
| count++; | |
| } | |
| } | |
| if (HasFailure()) return; | |
| } | |
| } | |
| } | |
| } | |
| // Helper function for testing vector-vector instructions that do not | |
| // use the value of the mask bit. | |
| template <typename Vd, typename Vs2, typename Vs1> | |
| void TernaryOpTestHelperVV(absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Vs1, Vd)> operation) { | |
| TernaryOpWithMaskTestHelperVV<Vd, Vs2, Vs1>( | |
| name, sew, inst, | |
| [operation](Vs2 vs2, Vs1 vs1, Vd vd, bool mask_value) -> Vd { | |
| if (mask_value) { | |
| return operation(vs2, vs1, vd); | |
| } | |
| return vd; | |
| }); | |
| } | |
| // Helper function for testing vector-scalar/immediate instructions that use | |
| // the value of the mask bit. | |
| template <typename Vd, typename Vs2, typename Rs1> | |
| void TernaryOpWithMaskTestHelperVX( | |
| absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Rs1, Vd, bool)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vd) && byte_sew != sizeof(Vs2) && | |
| byte_sew != sizeof(Rs1)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vd: " << sizeof(Vd) | |
| << " Vs2: " << sizeof(Vs2) << " Rs1: " << sizeof(Rs1); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vd_size = kVectorLengthInBytes / sizeof(Vd); | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| // Input values for 8 registers. | |
| Vd vd_value[vd_size * 8]; | |
| auto vd_span = Span<Vd>(vd_value); | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| AppendVectorRegisterOperands({kVs2}, {}); | |
| AppendRegisterOperands({kRs1Name}, {}); | |
| AppendVectorRegisterOperands({kVd, kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vd>(vd_span); | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| } | |
| // Iterate across the different lmul values. | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| // Try different vstart values. | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vd = lmul8 * sizeof(Vd) / byte_sew; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| // Set vstart, but leave vstart at 0 at least once. | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| ASSERT_TRUE(vlen > vstart); | |
| // Configure vector unit for the different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| // Reset Vd values, since the previous instruction execution | |
| // overwrites them. | |
| for (int i = 0; i < 8; i++) { | |
| auto vd_name = absl::StrCat("v", kVd + i); | |
| SetVectorRegisterValues<Vd>( | |
| {{vd_name, vd_span.subspan(vd_size * i, vd_size)}}); | |
| } | |
| // Generate a new rs1 value. | |
| CheriotRegister::ValueType rs1_reg_value = | |
| RandomValue<CheriotRegister::ValueType>(); | |
| SetRegisterValues<CheriotRegister::ValueType>( | |
| {{kRs1Name, rs1_reg_value}}); | |
| // Cast the value to the appropriate width, sign-extending if need | |
| // be. | |
| Rs1 rs1_value = static_cast<Rs1>( | |
| static_cast<typename SameSignedType<CheriotRegister::ValueType, | |
| Rs1>::type>(rs1_reg_value)); | |
| inst->Execute(); | |
| if ((std::min(lmul8_vs2, lmul8_vd) < 1) || | |
| (std::max(lmul8_vs2, lmul8_vd) > 64)) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()); | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| EXPECT_EQ(rv_vector_->vstart(), 0); | |
| int count = 0; | |
| for (int reg = kVd; reg < kVd + 8; reg++) { | |
| for (int i = 0; i < kVectorLengthInBytes / sizeof(Vd); i++) { | |
| int mask_index = count >> 3; | |
| int mask_offset = count & 0b111; | |
| bool mask_value = | |
| ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| // Compare elements that are between vstart and vlen for which | |
| // the mask is true. | |
| if ((count >= vstart) && (count < num_values)) { | |
| Vd expected_value = | |
| operation(vs2_value[count], static_cast<Rs1>(rs1_value), | |
| vd_value[count], mask_value); | |
| Vd inst_value = vreg_[reg]->data_buffer()->Get<Vd>(i); | |
| EXPECT_EQ(expected_value, inst_value) | |
| << "mask: " << mask_value << " (" << std::hex | |
| << (int64_t)vs2_value[count] << ", " << (int64_t)rs1_value | |
| << ") (" << std::dec << (int64_t)vs2_value[count] << ", " | |
| << (int64_t)rs1_value << ", " << (int64_t)vd_value[count] | |
| << ") " | |
| << absl::StrCat(name, "[", count, "] != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ") vstart(", vstart, | |
| ")"); | |
| } else { | |
| // The others should be zero. | |
| EXPECT_EQ(vd_span[count], vreg_[reg]->data_buffer()->Get<Vd>(i)) | |
| << absl::StrCat(name, " 0 != reg[", reg, "][", i, | |
| "] lmul8(", lmul8, ")"); | |
| } | |
| count++; | |
| } | |
| } | |
| if (HasFailure()) return; | |
| } | |
| } | |
| } | |
| } | |
| // Templated helper function that tests vector-scalar instructions that do | |
| // not use the value of the mask bit. | |
| template <typename Vd, typename Vs2, typename Rs1> | |
| void TernaryOpTestHelperVX(absl::string_view name, int sew, Instruction *inst, | |
| std::function<Vd(Vs2, Rs1, Vd)> operation) { | |
| TernaryOpWithMaskTestHelperVX<Vd, Vs2, Rs1>( | |
| name, sew, inst, | |
| [operation](Vs2 vs2, Rs1 rs1, Vd vd, bool mask_value) -> Vd { | |
| if (mask_value) { | |
| return operation(vs2, rs1, vd); | |
| } | |
| return vd; | |
| }); | |
| } | |
| // Helper function for testing binary mask vector-vector instructions that | |
| // use the mask bit. | |
| template <typename Vs2, typename Vs1> | |
| void BinaryMaskOpWithMaskTestHelperVV( | |
| absl::string_view name, int sew, Instruction *inst, | |
| std::function<uint8_t(Vs2, Vs1, bool)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vs2) && byte_sew != sizeof(Vs1)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vs2: " << sizeof(Vs2) | |
| << " Vs1: " << sizeof(Vs1); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1); | |
| // Input values for 8 registers. | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| Vs1 vs1_value[vs1_size * 8]; | |
| auto vs1_span = Span<Vs1>(vs1_value); | |
| AppendVectorRegisterOperands({kVs2, kVs1, kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| FillArrayWithRandomValues<Vs1>(vs1_span); | |
| // Make every third value the same (at least if the types are same sized). | |
| for (int i = 0; i < std::min(vs1_size, vs2_size); i += 3) { | |
| vs1_span[i] = static_cast<Vs1>(vs2_span[i]); | |
| } | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| auto vs1_name = absl::StrCat("v", kVs1 + i); | |
| SetVectorRegisterValues<Vs1>( | |
| {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}}); | |
| } | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| ClearVectorRegisterGroup(kVd, 8); | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| ASSERT_TRUE(vlen > vstart); | |
| // Configure vector unit for different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| inst->Execute(); | |
| if ((lmul8_vs2 < 1) || (lmul8_vs2 > 64)) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()); | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| EXPECT_EQ(rv_vector_->vstart(), 0); | |
| auto dest_span = vreg_[kVd]->data_buffer()->Get<uint8_t>(); | |
| for (int i = 0; i < kVectorLengthInBytes * 8; i++) { | |
| int mask_index = i >> 3; | |
| int mask_offset = i & 0b111; | |
| bool mask_value = ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| uint8_t inst_value = dest_span[i >> 3]; | |
| inst_value = (inst_value >> mask_offset) & 0b1; | |
| if ((i >= vstart) && (i < num_values)) { | |
| uint8_t expected_value = | |
| operation(vs2_value[i], vs1_value[i], mask_value); | |
| EXPECT_EQ(expected_value, inst_value) << absl::StrCat( | |
| name, "[", i, "] != reg[][", i, "] lmul8(", lmul8, | |
| ") vstart(", vstart, ") num_values(", num_values, ")"); | |
| } else { | |
| EXPECT_EQ(0, inst_value) << absl::StrCat( | |
| name, "[", i, "] 0 != reg[][", i, "] lmul8(", lmul8, | |
| ") vstart(", vstart, ") num_values(", num_values, ")"); | |
| } | |
| } | |
| if (HasFailure()) return; | |
| } | |
| } | |
| } | |
| } | |
| // Helper function for testing binary mask vector-vector instructions that do | |
| // not use the mask bit. | |
| template <typename Vs2, typename Vs1> | |
| void BinaryMaskOpTestHelperVV(absl::string_view name, int sew, | |
| Instruction *inst, | |
| std::function<uint8_t(Vs2, Vs1)> operation) { | |
| BinaryMaskOpWithMaskTestHelperVV<Vs2, Vs1>( | |
| name, sew, inst, | |
| [operation](Vs2 vs2, Vs1 vs1, bool mask_value) -> uint8_t { | |
| if (mask_value) { | |
| return operation(vs2, vs1); | |
| } | |
| return 0; | |
| }); | |
| } | |
| // Helper function for testing mask vector-scalar/immediate instructions that | |
| // use the mask bit. | |
| template <typename Vs2, typename Rs1> | |
| void BinaryMaskOpWithMaskTestHelperVX( | |
| absl::string_view name, int sew, Instruction *inst, | |
| std::function<uint8_t(Vs2, Rs1, bool)> operation) { | |
| int byte_sew = sew / 8; | |
| if (byte_sew != sizeof(Vs2) && byte_sew != sizeof(Rs1)) { | |
| FAIL() << name << ": selected element width != any operand types" | |
| << "sew: " << sew << " Vs2: " << sizeof(Vs2) | |
| << " Rs1: " << sizeof(Rs1); | |
| return; | |
| } | |
| // Number of elements per vector register. | |
| constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2); | |
| // Input values for 8 registers. | |
| Vs2 vs2_value[vs2_size * 8]; | |
| auto vs2_span = Span<Vs2>(vs2_value); | |
| AppendVectorRegisterOperands({kVs2}, {}); | |
| AppendRegisterOperands({kRs1Name}, {}); | |
| AppendVectorRegisterOperands({kVmask}, {kVd}); | |
| // Initialize input values. | |
| FillArrayWithRandomValues<Vs2>(vs2_span); | |
| SetVectorRegisterValues<uint8_t>( | |
| {{kVmaskName, Span<const uint8_t>(kA5Mask)}}); | |
| for (int i = 0; i < 8; i++) { | |
| auto vs2_name = absl::StrCat("v", kVs2 + i); | |
| SetVectorRegisterValues<Vs2>( | |
| {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}}); | |
| } | |
| for (int lmul_index = 0; lmul_index < 7; lmul_index++) { | |
| for (int vstart_count = 0; vstart_count < 4; vstart_count++) { | |
| for (int vlen_count = 0; vlen_count < 4; vlen_count++) { | |
| ClearVectorRegisterGroup(kVd, 8); | |
| int lmul8 = kLmul8Values[lmul_index]; | |
| int lmul8_vs2 = lmul8 * sizeof(Vs2) / byte_sew; | |
| int num_values = lmul8 * kVectorLengthInBytes / (8 * byte_sew); | |
| int vstart = 0; | |
| if (vstart_count > 0) { | |
| vstart = absl::Uniform(absl::IntervalOpen, bitgen_, 0, num_values); | |
| } | |
| // Set vlen, but leave vlen high at least once. | |
| int vlen = 1024; | |
| if (vlen_count > 0) { | |
| vlen = absl::Uniform(absl::IntervalOpenClosed, bitgen_, vstart, | |
| num_values); | |
| } | |
| num_values = std::min(num_values, vlen); | |
| ASSERT_TRUE(vlen > vstart); | |
| // Configure vector unit for different lmul settings. | |
| uint32_t vtype = (kSewSettingsByByteSize[byte_sew] << 3) | | |
| kLmulSettings[lmul_index]; | |
| ConfigureVectorUnit(vtype, vlen); | |
| rv_vector_->set_vstart(vstart); | |
| // Generate a new rs1 value. | |
| CheriotRegister::ValueType rs1_reg_value = | |
| RandomValue<CheriotRegister::ValueType>(); | |
| SetRegisterValues<CheriotRegister::ValueType>( | |
| {{kRs1Name, rs1_reg_value}}); | |
| // Cast the value to the appropriate width, sign-extending if need be. | |
| Rs1 rs1_value = static_cast<Rs1>( | |
| static_cast<typename SameSignedType<CheriotRegister::ValueType, | |
| Rs1>::type>(rs1_reg_value)); | |
| inst->Execute(); | |
| if ((lmul8_vs2 < 1) || (lmul8_vs2 > 64)) { | |
| EXPECT_TRUE(rv_vector_->vector_exception()); | |
| rv_vector_->clear_vector_exception(); | |
| continue; | |
| } | |
| EXPECT_FALSE(rv_vector_->vector_exception()); | |
| EXPECT_EQ(rv_vector_->vstart(), 0); | |
| auto dest_span = vreg_[kVd]->data_buffer()->Get<uint8_t>(); | |
| for (int i = 0; i < kVectorLengthInBytes * 8; i++) { | |
| int mask_index = i >> 3; | |
| int mask_offset = i & 0b111; | |
| bool mask_value = ((kA5Mask[mask_index] >> mask_offset) & 0b1) != 0; | |
| uint8_t inst_value = dest_span[i >> 3]; | |
| inst_value = (inst_value >> mask_offset) & 0b1; | |
| if ((i >= vstart) && (i < num_values)) { | |
| uint8_t expected_value = | |
| operation(vs2_value[i], rs1_value, mask_value); | |
| EXPECT_EQ(expected_value, inst_value) << absl::StrCat( | |
| name, "[i] != reg[0][", i, "] lmul8(", lmul8, ")"); | |
| } else { | |
| EXPECT_EQ(0, inst_value) << absl::StrCat( | |
| name, " 0 != reg[0][", i, "] lmul8(", lmul8, ")"); | |
| } | |
| } | |
| if (HasFailure()) return; | |
| } | |
| } | |
| } | |
| } | |
| // Helper function for testing mask vector-vector instructions that do not | |
| // use the mask bit. | |
| template <typename Vs2, typename Vs1> | |
| void BinaryMaskOpTestHelperVX(absl::string_view name, int sew, | |
| Instruction *inst, | |
| std::function<uint8_t(Vs2, Vs1)> operation) { | |
| BinaryMaskOpWithMaskTestHelperVX<Vs2, Vs1>( | |
| name, sew, inst, | |
| [operation](Vs2 vs2, Vs1 vs1, bool mask_value) -> uint8_t { | |
| if (mask_value) { | |
| return operation(vs2, vs1); | |
| } | |
| return 0; | |
| }); | |
| } | |
| // Helper function to compute the rounding output bit. | |
| template <typename T> | |
| T RoundBits(int num_bits, T lost_bits) { | |
| bool bit_d = | |
| (num_bits == 0) ? false : ((lost_bits >> (num_bits - 1)) & 0b1) != 0; | |
| bool bit_d_minus_1 = | |
| (num_bits < 2) ? false : ((lost_bits >> (num_bits - 2)) & 0b1) != 0; | |
| bool bits_d_minus_2_to_0 = | |
| (num_bits < 3) ? false | |
| : (lost_bits & ~(std::numeric_limits<uint64_t>::max() | |
| << (num_bits - 2))) != 0; | |
| bool bits_d_minus_1_to_0 = | |
| (num_bits < 2) ? false | |
| : (lost_bits & ~(std::numeric_limits<uint64_t>::max() | |
| << (num_bits - 1))) != 0; | |
| switch (rv_vector_->vxrm()) { | |
| case 0: | |
| return bit_d_minus_1; | |
| case 1: | |
| return bit_d_minus_1 & (bits_d_minus_2_to_0 | bit_d); | |
| case 2: | |
| return 0; | |
| case 3: | |
| return !bit_d & bits_d_minus_1_to_0; | |
| default: | |
| return 0; | |
| } | |
| } | |
| CheriotVectorState *rv_vector() const { return rv_vector_; } | |
| absl::Span<RVVectorRegister *> vreg() { | |
| return absl::Span<RVVectorRegister *>(vreg_); | |
| } | |
| absl::Span<CheriotRegister *> creg() { | |
| return absl::Span<CheriotRegister *>(creg_); | |
| } | |
| absl::BitGen &bitgen() { return bitgen_; } | |
| Instruction *instruction() { return instruction_; } | |
| protected: | |
| CheriotRegister *creg_[32]; | |
| RVVectorRegister *vreg_[32]; | |
| RVFpRegister *freg_[32]; | |
| CheriotState *state_; | |
| Instruction *instruction_; | |
| Instruction *child_instruction_; | |
| TaggedFlatDemandMemory *memory_; | |
| CheriotVectorState *rv_vector_; | |
| absl::BitGen bitgen_; | |
| }; | |
| #endif // MPACT_RISCV_RISCV_TEST_RISCV_VECTOR_INSTRUCTIONS_TEST_BASE_H_ |