|  | // 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_ |