No public description PiperOrigin-RevId: 998094460 Change-Id: Ic3196b4f5d12ee42dc0a7f0755c2969188f3d59d
diff --git a/riscv/BUILD b/riscv/BUILD index 7ddc997..5176adf 100644 --- a/riscv/BUILD +++ b/riscv/BUILD
@@ -272,6 +272,7 @@ ":riscv_fp_state", ":riscv_g", ":riscv_state", + ":riscv_zvt_state", "@abseil-cpp//absl/log", "@abseil-cpp//absl/status", "@abseil-cpp//absl/strings", @@ -1741,7 +1742,9 @@ "@abseil-cpp//absl/log", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/memory", + "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", "@mpact-sim//mpact/sim/generic:core", "@mpact-sim//mpact/sim/generic:instruction", ], @@ -1756,7 +1759,10 @@ ":riscv_state", ":riscv_zvt_state", "@abseil-cpp//absl/base", - "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", "@abseil-cpp//absl/types:span", "@mpact-sim//mpact/sim/generic:core", "@mpact-sim//mpact/sim/generic:instruction",
diff --git a/riscv/riscv_vector_memory_instructions.cc b/riscv/riscv_vector_memory_instructions.cc index 5366199..cedc8df 100644 --- a/riscv/riscv_vector_memory_instructions.cc +++ b/riscv/riscv_vector_memory_instructions.cc
@@ -25,6 +25,7 @@ #include "riscv/riscv_register.h" #include "riscv/riscv_state.h" #include "riscv/riscv_vector_state.h" +#include "riscv/riscv_zvt_state.h" namespace mpact { namespace sim { @@ -160,6 +161,10 @@ void Vsetvl(bool rd_zero, bool rs1_zero, const Instruction* inst) { auto* rv_state = static_cast<RiscVState*>(inst->state()); auto* rv_vector = rv_state->rv_vector(); + auto m_result = GetMatrixState(inst); + if (m_result.ok()) { + (*m_result)->ResetMtype(); + } uint32_t vtype = GetInstructionSource<uint32_t>(inst, 1) & 0b1'1'111'111; // Get previous vtype. uint32_t prev_vtype = rv_vector->vtype();
diff --git a/riscv/riscv_zvt_instructions.cc b/riscv/riscv_zvt_instructions.cc index 9a7965b..4dfc74b 100644 --- a/riscv/riscv_zvt_instructions.cc +++ b/riscv/riscv_zvt_instructions.cc
@@ -19,7 +19,10 @@ #include <cstring> #include "absl/base/casts.h" -#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" #include "absl/types/span.h" #include "mpact/sim/generic/data_buffer.h" #include "mpact/sim/generic/instruction.h" @@ -73,18 +76,28 @@ const int kEffectiveTileEdge = tile_element_width < 64 ? matrix_state->tile_elements() : matrix_state->tile_elements() / 2; - CHECK(row >= 0 && row < kEffectiveTileEdge) << "row out of bounds"; - CHECK(col >= 0 && col < kEffectiveTileEdge) << "col out of bounds"; + if (row < 0 || row >= kEffectiveTileEdge || col < 0 || + col >= kEffectiveTileEdge) { + return 0; + } + auto load_elem = [](auto status_or_val) -> uint64_t { + if (!status_or_val.ok()) { + LOG(ERROR) << status_or_val.status(); + return 0; + } + return static_cast<uint64_t>(*status_or_val); + }; switch (tile_element_width) { case 8: - return matrix_state->GetElem<uint8_t>(kTile, row, col, 8); + return load_elem(matrix_state->GetElem<uint8_t>(kTile, row, col, 8)); case 16: - return matrix_state->GetElem<uint16_t>(kTile, row, col, 16); + return load_elem(matrix_state->GetElem<uint16_t>(kTile, row, col, 16)); case 32: - return matrix_state->GetElem<uint32_t>(kTile, row, col, 32); + return load_elem(matrix_state->GetElem<uint32_t>(kTile, row, col, 32)); case 64: - return matrix_state->GetElem<uint64_t>(kTile, row, col, 64); + return load_elem(matrix_state->GetElem<uint64_t>(kTile, row, col, 64)); default: + LOG(ERROR) << "Invalid tile element width: " << tile_element_width; return 0; } } @@ -94,24 +107,34 @@ const int kEffectiveTileEdge = tile_element_width < 64 ? matrix_state->tile_elements() : matrix_state->tile_elements() / 2; - CHECK(row >= 0 && row < kEffectiveTileEdge) << "row out of bounds"; - CHECK(col >= 0 && col < kEffectiveTileEdge) << "col out of bounds"; + if (row < 0 || row >= kEffectiveTileEdge || col < 0 || + col >= kEffectiveTileEdge) { + return; + } + absl::Status status; switch (tile_element_width) { case 8: - matrix_state->SetElem<uint8_t>(kTile, row, col, 8, - static_cast<uint8_t>(value)); + status = matrix_state->SetElem<uint8_t>(kTile, row, col, 8, + static_cast<uint8_t>(value)); break; case 16: - matrix_state->SetElem<uint16_t>(kTile, row, col, 16, - static_cast<uint16_t>(value)); + status = matrix_state->SetElem<uint16_t>(kTile, row, col, 16, + static_cast<uint16_t>(value)); break; case 32: - matrix_state->SetElem<uint32_t>(kTile, row, col, 32, - static_cast<uint32_t>(value)); + status = matrix_state->SetElem<uint32_t>(kTile, row, col, 32, + static_cast<uint32_t>(value)); break; case 64: - matrix_state->SetElem<uint64_t>(kTile, row, col, 64, value); + status = matrix_state->SetElem<uint64_t>(kTile, row, col, 64, value); break; + default: + status = absl::InvalidArgumentError( + absl::StrCat("Invalid tile element width: ", tile_element_width)); + break; + } + if (!status.ok()) { + LOG(ERROR) << status; } } @@ -449,20 +472,36 @@ for (int mm = 0; mm < kTileM; mm++) { for (int nn = 0; nn < kTileN; nn++) { if (kIsFp) { - float acc = + absl::StatusOr<float> acc_res = matrix_state->GetElem<float>(kDestTile, mm, nn, kTileElementWidth); + if (!acc_res.ok()) { + LOG(ERROR) << acc_res.status(); + return; + } + float acc = *acc_res; for (int kk = 0; kk < kTileK; kk++) { acc += read_fp(a, a_spans, kk, mm) * read_fp(b, b_spans, kk, nn); } - matrix_state->SetElem<float>(kDestTile, mm, nn, kTileElementWidth, acc); + // SetElem cannot fail because GetElem with the same parameters + // succeeded above. + matrix_state->SetElem<float>(kDestTile, mm, nn, kTileElementWidth, acc) + .IgnoreError(); } else { - int32_t acc = matrix_state->GetElem<int32_t>(kDestTile, mm, nn, - kTileElementWidth); + absl::StatusOr<int32_t> acc_res = matrix_state->GetElem<int32_t>( + kDestTile, mm, nn, kTileElementWidth); + if (!acc_res.ok()) { + LOG(ERROR) << acc_res.status(); + return; + } + int32_t acc = *acc_res; for (int kk = 0; kk < kTileK; kk++) { acc += read_int(a, a_spans, kk, mm) * read_int(b, b_spans, kk, nn); } - matrix_state->SetElem<int32_t>(kDestTile, mm, nn, kTileElementWidth, - acc); + // SetElem cannot fail because GetElem with the same parameters + // succeeded above. + matrix_state + ->SetElem<int32_t>(kDestTile, mm, nn, kTileElementWidth, acc) + .IgnoreError(); } } }
diff --git a/riscv/riscv_zvt_state.cc b/riscv/riscv_zvt_state.cc index a00168d..d1327a0 100644 --- a/riscv/riscv_zvt_state.cc +++ b/riscv/riscv_zvt_state.cc
@@ -21,21 +21,73 @@ #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/memory/memory.h" +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" #include "mpact/sim/generic/data_buffer.h" #include "mpact/sim/generic/instruction.h" #include "riscv/riscv_csr.h" #include "riscv/riscv_state.h" #include "riscv/riscv_vector_state.h" +#include "riscv/riscv_xstatus.h" namespace mpact::sim::riscv { -uint32_t RiscVMtypeCsr::AsUint32() { return matrix_state_->mtype(); } -uint64_t RiscVMtypeCsr::AsUint64() { return matrix_state_->mtype(); } +namespace { +constexpr uint32_t kMstatusVsMask = 0x0600U; +} // namespace + +uint32_t RiscVMtypeCsr::AsUint32() { + auto* state = matrix_state_->riscv_state(); + if ((state->mstatus()->GetUint32() & kMstatusVsMask) == 0) { + state->Trap( + /*is_interrupt=*/false, 0, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction), + 0, nullptr); + return 0; + } + return matrix_state_->mtype(); +} +uint64_t RiscVMtypeCsr::AsUint64() { + auto* state = matrix_state_->riscv_state(); + if ((state->mstatus()->GetUint32() & kMstatusVsMask) == 0) { + state->Trap( + /*is_interrupt=*/false, 0, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction), + 0, nullptr); + return 0; + } + return matrix_state_->mtype(); +} + +void RiscVMtypeCsr::Write(uint32_t /*value*/) { + matrix_state_->riscv_state()->Trap( + /*is_interrupt=*/false, 0, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction), + 0, nullptr); +} + +void RiscVMtypeCsr::Write(uint64_t /*value*/) { + matrix_state_->riscv_state()->Trap( + /*is_interrupt=*/false, 0, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction), + 0, nullptr); +} absl::StatusOr<std::unique_ptr<RiscVZvtMatrixState>> RiscVZvtMatrixState::Create( ::mpact::sim::riscv::RiscVState* /* absl_nonnull */ state, int tile_elements) { + if (tile_elements < 4) { + return absl::InvalidArgumentError("Tile element width must be >= 4"); + } + if ((tile_elements & (tile_elements - 1)) != 0) { + return absl::InvalidArgumentError( + absl::StrCat("TE must be a power of two: ", tile_elements)); + } auto matrix = absl::WrapUnique(new RiscVZvtMatrixState(state, tile_elements)); auto status = state->csr_set()->AddCsr(&matrix->mtype_csr_); if (!status.ok()) return status; @@ -47,29 +99,21 @@ int tile_elements) : state_(state), tile_elements_(tile_elements), mtype_csr_(this, state) { // TE must be a power of two in [4, VLEN/4]. VLEN/4 = 32 for VLEN=128b. - CHECK_GE(tile_elements_, 4) << "Tile element width must be >= 4"; - CHECK_EQ(tile_elements_ & (tile_elements_ - 1), 0) - << "TE must be a power of two: " << tile_elements_; - tile_buffer_ = state_->db_factory()->Allocate<uint8_t>(16 * tile_elements * - tile_elements); + tile_buffer_.reset(state_->db_factory()->Allocate<uint8_t>( + 16 * tile_elements * tile_elements)); for (auto& byte : tile_buffer_->Get<uint8_t>()) { byte = 0; } } -RiscVZvtMatrixState::~RiscVZvtMatrixState() { - if (tile_buffer_ != nullptr) tile_buffer_->DecRef(); - for (auto* db : scratch_dbs_) { - if (db != nullptr) db->DecRef(); - } -} +RiscVZvtMatrixState::~RiscVZvtMatrixState() = default; ::mpact::sim::generic::DataBuffer* RiscVZvtMatrixState::GetScratchDb(int size) { - if (size >= scratch_dbs_.size()) scratch_dbs_.resize(size + 1, nullptr); + if (size >= scratch_dbs_.size()) scratch_dbs_.resize(size + 1); if (!scratch_dbs_[size]) { - scratch_dbs_[size] = state_->db_factory()->Allocate<uint8_t>(size); + scratch_dbs_[size].reset(state_->db_factory()->Allocate<uint8_t>(size)); } - return scratch_dbs_[size]; + return scratch_dbs_[size].get(); } int RiscVZvtMatrixState::sew_bytes() const { @@ -79,9 +123,9 @@ return rv_vector == nullptr ? 1 : rv_vector->selected_element_width(); } -// Transcribed verbatim from the Zvt v0.3 spec (15.1.1.1, "Tile Punning"). -int RiscVZvtMatrixState::Punning(int tile, int row, int col, - int tile_element_width) const { +// Transcribed verbatim from the Zvt v0.4 spec ("Tile Punning"). +absl::StatusOr<int> RiscVZvtMatrixState::GetPunningOffset( + int tile, int row, int col, int tile_element_width) const { const int kTileElements = tile_elements_; int ptile, minor_offset, major_offset; switch (tile_element_width) { @@ -112,12 +156,35 @@ break; } default: - LOG(FATAL) << "Invalid tile element width: " << tile_element_width; + return absl::InvalidArgumentError( + absl::StrCat("Invalid tile element width: ", tile_element_width)); } return (ptile * kTileElements * kTileElements) + (major_offset * 16) + minor_offset; } +namespace { +int GetTileAlignment(int tile_element_width_bits) { + int ntiles = 16; + if (tile_element_width_bits == 16) { + ntiles = 8; + } else if (tile_element_width_bits == 32) { + ntiles = 4; + } else if (tile_element_width_bits == 64) { + ntiles = 8; + } + return 16 / ntiles; +} +} // namespace + +bool RiscVZvtMatrixState::IsValidTile(int tile) const { + return (tile % GetTileAlignment(tile_element_width_bits())) == 0; +} + +int RiscVZvtMatrixState::MaskTssTile(int tile, int tile_element_width) const { + return tile & ~(GetTileAlignment(tile_element_width) - 1); +} + absl::StatusOr<RiscVZvtMatrixState*> GetMatrixState( const ::mpact::sim::generic::Instruction* inst) { auto* rv_state = static_cast<::mpact::sim::riscv::RiscVState*>(inst->state());
diff --git a/riscv/riscv_zvt_state.h b/riscv/riscv_zvt_state.h index 40a1007..a82a1a5 100644 --- a/riscv/riscv_zvt_state.h +++ b/riscv/riscv_zvt_state.h
@@ -13,13 +13,13 @@ // limitations under the License. // Architectural state for the RISC-V Vector Matrix Extension (Zvt / "VME", -// spec v0.3, chapter 15.1). This is layered on top of the existing RISC-V +// spec v0.4). This is layered on top of the existing RISC-V // vector state: the matrix unit sources its A/B operands from vector registers // and accumulates results into the new square matrix "tile" state modeled here. // -// NOTE: Zvt is a draft (v0.3) extension. The mtype CSR field positions below -// follow the v0.3 manual (15.1.1.2). They are isolated as named constants so -// they can be adjusted if the encoding firms up. +// NOTE: mtype CSR field positions below follow the v0.4 manual. +// They are isolated as named constants so they can be adjusted if the +// encoding firms up. #ifndef THIRD_PARTY_MPACT_RISCV_RISCV_ZVT_STATE_H_ #define THIRD_PARTY_MPACT_RISCV_RISCV_ZVT_STATE_H_ @@ -30,6 +30,7 @@ #include <vector> #include "absl/base/nullability.h" +#include "absl/status/status.h" #include "absl/status/statusor.h" #include "mpact/sim/generic/data_buffer.h" #include "mpact/sim/generic/instruction.h" @@ -81,6 +82,10 @@ uint32_t AsUint32() override; uint64_t AsUint64() override; + // Software writes to mtype are illegal; they must trap. + void Write(uint32_t value) override; + void Write(uint64_t value) override; + [[nodiscard]] RiscVZvtMatrixState* matrix_state() const { return matrix_state_; } @@ -90,8 +95,17 @@ }; // Matrix tile state. Owns the flat tile buffer (16 * TE * TE bytes) and the -// mtype CSR. All element access goes through Punning()/GetElem()/SetElem() so -// the tile-aliasing ("punning") layout is implemented exactly once. +// mtype CSR. All element access goes through GetPunningOffset()/GetElem()/ +// SetElem() so the tile-aliasing ("punning") layout is implemented exactly +// once. +struct DataBufferDeleter { + void operator()(::mpact::sim::generic::DataBuffer* db) const { + if (db) db->DecRef(); + } +}; +using SafeDataBuffer = + std::unique_ptr<::mpact::sim::generic::DataBuffer, DataBufferDeleter>; + class RiscVZvtMatrixState { public: static absl::StatusOr<std::unique_ptr<RiscVZvtMatrixState>> Create( @@ -112,30 +126,44 @@ // Byte offset into tile_buffer_ for element (tile,row,col) at the given tile // element width `tile_element_width` (in bits: 8/16/32/64). Transcribed - // verbatim from the Zvt v0.3 spec (15.1.1.1, "Tile Punning"). `tile` is the + // verbatim from the Zvt v0.4 spec ("Tile Punning"). `tile` is the // named tile index (0..15); `tile_element_width` selects the aliasing scheme. - int Punning(int tile, int row, int col, int tile_element_width) const; + absl::StatusOr<int> GetPunningOffset(int tile, int row, int col, + int tile_element_width) const; template <typename T> - T GetElem(int tile, int row, int col, int tile_element_width) const { + absl::StatusOr<T> GetElem(int tile, int row, int col, + int tile_element_width) const { T value; auto span = tile_buffer_->Get<uint8_t>(); - std::memcpy(&value, &span[Punning(tile, row, col, tile_element_width)], - sizeof(T)); + absl::StatusOr<int> offset = + GetPunningOffset(tile, row, col, tile_element_width); + if (!offset.ok()) return offset.status(); + std::memcpy(&value, &span[*offset], sizeof(T)); return value; } template <typename T> - void SetElem(int tile, int row, int col, int tile_element_width, T value) { + absl::Status SetElem(int tile, int row, int col, int tile_element_width, + T value) { auto span = tile_buffer_->Get<uint8_t>(); - std::memcpy(&span[Punning(tile, row, col, tile_element_width)], &value, - sizeof(T)); + absl::StatusOr<int> offset = + GetPunningOffset(tile, row, col, tile_element_width); + if (!offset.ok()) return offset.status(); + std::memcpy(&span[*offset], &value, sizeof(T)); + return absl::OkStatus(); } // mtype accessors. The mtype value is held here; the CSR is a read-only view. [[nodiscard]] uint32_t mtype() const { return mtype_; } void set_mtype(uint32_t value) { mtype_ = value; } + void ResetMtype() { set_mtype(0); } + + // Tile index validation and masking (Zvt v0.4). + [[nodiscard]] bool IsValidTile(int tile) const; + [[nodiscard]] int MaskTssTile(int tile, int tile_element_width) const; + // Decoded mtype fields. [[nodiscard]] int mtwiden() const { return (mtype() >> kMtypeMtwidenShift) & kMtypeMtwidenMask; @@ -188,8 +216,8 @@ ::mpact::sim::riscv::RiscVState* state_; int tile_elements_; - ::mpact::sim::generic::DataBuffer* tile_buffer_; // 16 * TE * TE bytes. - std::vector<::mpact::sim::generic::DataBuffer*> scratch_dbs_; + SafeDataBuffer tile_buffer_; // 16 * TE * TE bytes. + std::vector<SafeDataBuffer> scratch_dbs_; uint32_t mtype_ = 0; RiscVMtypeCsr mtype_csr_; };
diff --git a/riscv/test/BUILD b/riscv/test/BUILD index 749e73e..9a65dba 100644 --- a/riscv/test/BUILD +++ b/riscv/test/BUILD
@@ -1026,6 +1026,22 @@ ], ) +cc_test( + name = "riscv_zvt_state_test", + size = "small", + srcs = ["riscv_zvt_state_test.cc"], + deps = [ + "//riscv:riscv_state", + "//riscv:riscv_v", + "//riscv:riscv_zvt_state", + "@abseil-cpp//absl/status", + "@com_google_googletest//:gtest_main", + "@mpact-sim//mpact/sim/generic:core", + "@mpact-sim//mpact/sim/generic:instruction", + "@mpact-sim//mpact/sim/util/memory", + ], +) + config_setting( name = "arm_cpu", values = {"cpu": "arm"},
diff --git a/riscv/test/riscv_zvt_state_test.cc b/riscv/test/riscv_zvt_state_test.cc new file mode 100644 index 0000000..dc71a23 --- /dev/null +++ b/riscv/test/riscv_zvt_state_test.cc
@@ -0,0 +1,728 @@ +// Copyright 2026 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. + +#include "riscv/riscv_zvt_state.h" + +#include <cstdint> +#include <memory> +#include <utility> + +#include "absl/status/status.h" +#include "googlemock/include/gmock/gmock.h" +#include "mpact/sim/generic/immediate_operand.h" // NOLINT(misc-include-cleaner) +#include "mpact/sim/generic/instruction.h" +#include "mpact/sim/util/memory/flat_demand_memory.h" +#include "riscv/riscv_register.h" +#include "riscv/riscv_state.h" +#include "riscv/riscv_vector_memory_instructions.h" +#include "riscv/riscv_vector_state.h" + +/* start - helper test macro */ +#define CONCAT_INNER(a, b) a##b +#define CONCAT(a, b) CONCAT_INNER(a, b) +#define ASSERT_OK(expression) ASSERT_TRUE((expression).ok()) +#define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ + auto CONCAT(status_or_line_, __LINE__) = (rexpr); \ + ASSERT_OK(CONCAT(status_or_line_, __LINE__)); \ + lhs = std::move(CONCAT(status_or_line_, __LINE__)).value(); +/* end - helper test macro */ + +namespace { + +using ::mpact::sim::riscv::RiscVState; +using ::mpact::sim::riscv::RiscVXlen; +using ::mpact::sim::riscv::RiscVZvtMatrixState; +using ::mpact::sim::util::FlatDemandMemory; + +TEST(RiscVZvtStateTest, MtypeLayoutConfiguration) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + // Test mtwiden (bits 0-1) + matrix_state->set_mtype(0x3); + EXPECT_EQ(matrix_state->mtwiden(), 3); + EXPECT_EQ(matrix_state->twiden(), 4); + EXPECT_TRUE(matrix_state->configured()); + + // Test unconfigured (mtwiden = 0) + matrix_state->set_mtype(0x0); + EXPECT_EQ(matrix_state->mtwiden(), 0); + EXPECT_EQ(matrix_state->twiden(), 0); + EXPECT_FALSE(matrix_state->configured()); + + // Test tk (bits 5-7) + matrix_state->set_mtype(0x7 << 5); + EXPECT_EQ(matrix_state->tile_k(), 7); + + // Test tm (bits 10-23) + matrix_state->set_mtype(0x3FFF << 10); + EXPECT_EQ(matrix_state->tile_m(), 0x3FFF); +} + +TEST(RiscVZvtStateTest, MtypeCsrWriteTrapsIllegalInstruction) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + state->mstatus()->Set((state->mstatus()->GetUint32() & ~0x0600U) | (1U << 9)); + + // Fetch CSR using the constant address. + ASSERT_OK_AND_ASSIGN(auto* csr, state->csr_set()->GetCsr( + mpact::sim::riscv::kRiscVMtypeCsrIndex)); + auto* mtype_csr = csr; + + // Set up the trap callback. + bool trap_taken = false; + uint64_t trap_code = 0; + state->set_on_trap([&](bool, uint64_t, uint64_t exception_code, uint64_t, + const ::mpact::sim::generic::Instruction*) { + trap_taken = true; + trap_code = exception_code; + return true; + }); + + // Attempt to write via CSR interface. This must trap since mtype is + // read-only. + mtype_csr->Write(static_cast<uint32_t>(0xDEADBEEF)); + EXPECT_TRUE(trap_taken); + EXPECT_EQ(trap_code, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction)); + + trap_taken = false; + trap_code = 0; + mtype_csr->Write(static_cast<uint64_t>(0xDEADBEEF)); + EXPECT_TRUE(trap_taken); + EXPECT_EQ(trap_code, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction)); +} + +struct PunningOffsets { + int ptile; + int minor_offset; + int major_offset; + int expected_offset; +}; + +// Number of architectural tile-specifier steps between valid tiles for a given +// `tile_element_width` (Zvt v0.4 Section "Matrix Tile State"): +// TEW=8 -> 16 tiles (step 1: mt0, mt1, ..., mt15) +// TEW=16 -> 8 tiles (step 2: mt0, mt2, ..., mt14) +// TEW=32 -> 4 tiles (step 4: mt0, mt4, mt8, mt12) +// TEW=64 -> 8 tiles (step 2: mt0, mt2, ..., mt14) +int TileAlignmentForTew(int tile_element_width) { + switch (tile_element_width) { + case 8: + return 1; + case 16: + case 64: + return 2; + case 32: + return 4; + default: + return 1; + } +} + +// Effective number of elements along a tile edge (ETE) as a function of +// `tile_elements` (TE) and `tile_element_width` (TEW) (Zvt v0.4): +// ETE = (TEW < 64) ? TE : (TE / 2). +int EffectiveTileEdge(int tile_elements, int tile_element_width) { + return tile_element_width < 64 ? tile_elements : tile_elements / 2; +} + +// Computes the Zvt v0.4 Section "Tile Punning" decomposition: +// - `ptile`: physical TEW=8 tile index (0..15) in the 16*TE*TE-byte buffer +// - `minor_offset`: byte offset (0..15) within the 16-byte minor block +// - `major_offset`: index of the 16-byte minor block within `ptile` +// - `expected_offset`: linear byte offset in `tile_buffer_` +PunningOffsets ComputeExpectedPunningOffsets(int tile_elements, int tile, + int row, int col, + int tile_element_width) { + constexpr int kMinorBlockBytes = 16; + constexpr int kSubblockEdge = 4; + const int kSubblocksPerEdge = tile_elements / kSubblockEdge; + int ptile = 0; + int minor_offset = 0; + int major_offset = 0; + switch (tile_element_width) { + case 8: + ptile = tile; + minor_offset = + (row % kSubblockEdge) * kSubblockEdge + (col % kSubblockEdge); + major_offset = + (row / kSubblockEdge) * kSubblocksPerEdge + (col / kSubblockEdge); + break; + case 16: + ptile = tile + ((row & 2) >> 1); + minor_offset = (row % 2) * 4 + (col % 2) * 2 + ((col / 2) % 2) * 8; + major_offset = + (row / kSubblockEdge) * kSubblocksPerEdge + (col / kSubblockEdge); + break; + case 32: + ptile = tile + 2 * (row / (tile_elements / 2)) + ((col & 2) >> 1); + minor_offset = (row % 2) * 8 + (col % 2) * 4; + major_offset = ((row / 2) % kSubblocksPerEdge) * kSubblocksPerEdge + + (col / kSubblockEdge); + break; + case 64: + ptile = tile + (row / kSubblocksPerEdge); + minor_offset = (col % 2) * 8; + major_offset = (row % kSubblocksPerEdge) * kSubblocksPerEdge + (col / 2); + break; + } + int expected_offset = (ptile * tile_elements * tile_elements) + + (major_offset * kMinorBlockBytes) + minor_offset; + return {ptile, minor_offset, major_offset, expected_offset}; +} + +TEST(RiscVZvtStateTest, PunningTEW8) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + constexpr int kTileElements = 32; + constexpr int kTileElementWidth = 8; + const int kTileAlignment = TileAlignmentForTew(kTileElementWidth); + const int kEffectiveTileEdge = + EffectiveTileEdge(kTileElements, kTileElementWidth); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), kTileElements)); + + // 1) Origin corner: first valid tile (0), row 0, col 0. + ASSERT_OK_AND_ASSIGN( + int offset0, + matrix_state->GetPunningOffset(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset0, 0); + + // 2) Interior element: aligned tile specifier and interior (row, col) with + // non-zero quotients and remainders modulo 2 and 4. + const int kInteriorTile = 3 * kTileAlignment; + constexpr int kInteriorRow = 10; + constexpr int kInteriorCol = 7; + auto [ptile1, minor_offset1, major_offset1, expected_offset1] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kInteriorTile, + /*row=*/kInteriorRow, + /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_LT(ptile1, 16); + EXPECT_LT(minor_offset1, 16); + EXPECT_LT(major_offset1, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset1, + matrix_state->GetPunningOffset(/*tile=*/kInteriorTile, + /*row=*/kInteriorRow, /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset1, expected_offset1); + + // 3) Upper-bound corner: last valid tile and last (row, col) within ETE. + const int kMaxValidTile = 16 - kTileAlignment; + const int kMaxRow = kEffectiveTileEdge - 1; + const int kMaxCol = kEffectiveTileEdge - 1; + auto [ptile2, minor_offset2, major_offset2, expected_offset2] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kMaxValidTile, + /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_EQ(ptile2, 15); + EXPECT_LT(minor_offset2, 16); + EXPECT_LT(major_offset2, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset2, + matrix_state->GetPunningOffset(/*tile=*/kMaxValidTile, /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset2, expected_offset2); + EXPECT_EQ(offset2, + matrix_state->tile_buffer_bytes() - (kTileElementWidth / 8)); +} + +TEST(RiscVZvtStateTest, PunningTEW16) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + constexpr int kTileElements = 32; + constexpr int kTileElementWidth = 16; + const int kTileAlignment = TileAlignmentForTew(kTileElementWidth); + const int kEffectiveTileEdge = + EffectiveTileEdge(kTileElements, kTileElementWidth); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), kTileElements)); + + // 1) Origin corner: first valid tile (0), row 0, col 0. + ASSERT_OK_AND_ASSIGN( + int offset0, + matrix_state->GetPunningOffset(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset0, 0); + + // 2) Interior element: aligned tile specifier and interior (row, col). + const int kInteriorTile = 3 * kTileAlignment; + constexpr int kInteriorRow = 10; + constexpr int kInteriorCol = 7; + auto [ptile1, minor_offset1, major_offset1, expected_offset1] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kInteriorTile, + /*row=*/kInteriorRow, + /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_LT(ptile1, 16); + EXPECT_LT(minor_offset1, 16); + EXPECT_LT(major_offset1, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset1, + matrix_state->GetPunningOffset(/*tile=*/kInteriorTile, + /*row=*/kInteriorRow, /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset1, expected_offset1); + + // 3) Upper-bound corner: last valid tile and last (row, col) within ETE. + const int kMaxValidTile = 16 - kTileAlignment; + const int kMaxRow = kEffectiveTileEdge - 1; + const int kMaxCol = kEffectiveTileEdge - 1; + auto [ptile2, minor_offset2, major_offset2, expected_offset2] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kMaxValidTile, + /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_EQ(ptile2, 15); + EXPECT_LT(minor_offset2, 16); + EXPECT_LT(major_offset2, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset2, + matrix_state->GetPunningOffset(/*tile=*/kMaxValidTile, /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset2, expected_offset2); + EXPECT_EQ(offset2, + matrix_state->tile_buffer_bytes() - (kTileElementWidth / 8)); +} + +TEST(RiscVZvtStateTest, PunningTEW32) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + constexpr int kTileElements = 32; + constexpr int kTileElementWidth = 32; + const int kTileAlignment = TileAlignmentForTew(kTileElementWidth); + const int kEffectiveTileEdge = + EffectiveTileEdge(kTileElements, kTileElementWidth); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), kTileElements)); + + // 1) Origin corner: first valid tile (0), row 0, col 0. + ASSERT_OK_AND_ASSIGN( + int offset0, + matrix_state->GetPunningOffset(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset0, 0); + + // 2) Interior element: aligned tile specifier and interior (row, col). + const int kInteriorTile = 3 * kTileAlignment; + constexpr int kInteriorRow = 10; + constexpr int kInteriorCol = 7; + auto [ptile1, minor_offset1, major_offset1, expected_offset1] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kInteriorTile, + /*row=*/kInteriorRow, + /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_LT(ptile1, 16); + EXPECT_LT(minor_offset1, 16); + EXPECT_LT(major_offset1, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset1, + matrix_state->GetPunningOffset(/*tile=*/kInteriorTile, + /*row=*/kInteriorRow, /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset1, expected_offset1); + + // 3) Upper-bound corner: last valid tile and last (row, col) within ETE. + const int kMaxValidTile = 16 - kTileAlignment; + const int kMaxRow = kEffectiveTileEdge - 1; + const int kMaxCol = kEffectiveTileEdge - 1; + auto [ptile2, minor_offset2, major_offset2, expected_offset2] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kMaxValidTile, + /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_EQ(ptile2, 15); + EXPECT_LT(minor_offset2, 16); + EXPECT_LT(major_offset2, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset2, + matrix_state->GetPunningOffset(/*tile=*/kMaxValidTile, /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset2, expected_offset2); + EXPECT_EQ(offset2, + matrix_state->tile_buffer_bytes() - (kTileElementWidth / 8)); +} + +TEST(RiscVZvtStateTest, PunningTEW64) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + constexpr int kTileElements = 32; + constexpr int kTileElementWidth = 64; + const int kTileAlignment = TileAlignmentForTew(kTileElementWidth); + const int kEffectiveTileEdge = + EffectiveTileEdge(kTileElements, kTileElementWidth); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), kTileElements)); + + // 1) Origin corner: first valid tile (0), row 0, col 0. + ASSERT_OK_AND_ASSIGN( + int offset0, + matrix_state->GetPunningOffset(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset0, 0); + + // 2) Interior element: aligned tile specifier and interior (row, col). + const int kInteriorTile = 3 * kTileAlignment; + constexpr int kInteriorRow = 10; + constexpr int kInteriorCol = 7; + auto [ptile1, minor_offset1, major_offset1, expected_offset1] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kInteriorTile, + /*row=*/kInteriorRow, + /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_LT(ptile1, 16); + EXPECT_LT(minor_offset1, 16); + EXPECT_LT(major_offset1, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset1, + matrix_state->GetPunningOffset(/*tile=*/kInteriorTile, + /*row=*/kInteriorRow, /*col=*/kInteriorCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset1, expected_offset1); + + // 3) Upper-bound corner: last valid tile and last (row, col) within ETE. + const int kMaxValidTile = 16 - kTileAlignment; + const int kMaxRow = kEffectiveTileEdge - 1; + const int kMaxCol = kEffectiveTileEdge - 1; + auto [ptile2, minor_offset2, major_offset2, expected_offset2] = + ComputeExpectedPunningOffsets(/*tile_elements=*/kTileElements, + /*tile=*/kMaxValidTile, + /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth); + EXPECT_EQ(ptile2, 15); + EXPECT_LT(minor_offset2, 16); + EXPECT_LT(major_offset2, (kTileElements / 4) * (kTileElements / 4)); + ASSERT_OK_AND_ASSIGN( + int offset2, + matrix_state->GetPunningOffset(/*tile=*/kMaxValidTile, /*row=*/kMaxRow, + /*col=*/kMaxCol, + /*tile_element_width=*/kTileElementWidth)); + EXPECT_EQ(offset2, expected_offset2); + EXPECT_EQ(offset2, + matrix_state->tile_buffer_bytes() - (kTileElementWidth / 8)); +} + +TEST(RiscVZvtStateTest, TileSpecifierAlignmentAndMasking) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + // Without vector state, sew_bytes() defaults to 1 (SEW = 8 bits). + // tew = sew(8) * twiden. + // We manipulate twiden via mtwiden. twiden = 1 << (mtwiden - 1). + + // TEW=8 (mtwiden=1 => twiden=1). NTiles = 16. align = 1. + matrix_state->set_mtype(1 << mpact::sim::riscv::kMtypeMtwidenShift); + EXPECT_TRUE(matrix_state->IsValidTile(15)); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/15, /*tile_element_width=*/8), + 15); + + // TEW=16 (mtwiden=2 => twiden=2). NTiles = 8. align = 2. + matrix_state->set_mtype(2 << mpact::sim::riscv::kMtypeMtwidenShift); + EXPECT_TRUE(matrix_state->IsValidTile(0)); + EXPECT_TRUE(matrix_state->IsValidTile(2)); + EXPECT_FALSE(matrix_state->IsValidTile(3)); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/3, /*tile_element_width=*/16), + 2); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/15, /*tile_element_width=*/16), + 14); + + // TEW=32 (mtwiden=3 => twiden=4). NTiles = 4. align = 4. + matrix_state->set_mtype(3 << mpact::sim::riscv::kMtypeMtwidenShift); + EXPECT_TRUE(matrix_state->IsValidTile(4)); + EXPECT_FALSE(matrix_state->IsValidTile(5)); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/7, /*tile_element_width=*/32), + 4); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/15, /*tile_element_width=*/32), + 12); +} + +TEST(RiscVZvtStateTest, TileSpecifierAlignmentAndMasking64) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + auto vector_state = + std::make_unique<mpact::sim::riscv::RiscVVectorState>(state.get(), 16); + // vtype[5:3] = 1 (SEW=16 bits) + vector_state->SetVectorType(1 << 3); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + // TEW=64 (sew=16, mtwiden=3 => twiden=4). NTiles = 8. align = 2. + matrix_state->set_mtype(3 << mpact::sim::riscv::kMtypeMtwidenShift); + EXPECT_TRUE(matrix_state->IsValidTile(0)); + EXPECT_TRUE(matrix_state->IsValidTile(2)); + EXPECT_TRUE(matrix_state->IsValidTile(8)); + EXPECT_FALSE(matrix_state->IsValidTile(9)); + EXPECT_FALSE(matrix_state->IsValidTile(3)); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/15, /*tile_element_width=*/64), + 14); + EXPECT_EQ(matrix_state->MaskTssTile(/*tile=*/7, /*tile_element_width=*/64), + 6); +} + +TEST(RiscVZvtStateTest, VsetvlZeroesMtype) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + state->mstatus()->Set((state->mstatus()->GetUint32() & ~0x0600U) | (1U << 9)); + auto vector_state = + std::make_unique<mpact::sim::riscv::RiscVVectorState>(state.get(), 16); + vector_state->SetVectorType(0); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + matrix_state->set_mtype(0xFFFFFFFF); + mpact::sim::generic::Instruction inst(0, state.get()); + inst.AppendSource(new mpact::sim::generic::ImmediateOperand<uint32_t>(32)); + inst.AppendSource(new mpact::sim::generic::ImmediateOperand<uint32_t>(0)); + auto* reg = state->GetRegister<mpact::sim::riscv::RV32Register>("x1").first; + inst.AppendDestination(reg->CreateDestinationOperand(0)); + + mpact::sim::riscv::Vsetvl(/*rd_zero=*/false, /*rs1_zero=*/false, &inst); + EXPECT_EQ(matrix_state->mtype(), 0); +} + +TEST(RiscVZvtStateTest, VsetvliZeroesMtype) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + state->mstatus()->Set((state->mstatus()->GetUint32() & ~0x0600U) | (1U << 9)); + auto vector_state = + std::make_unique<mpact::sim::riscv::RiscVVectorState>(state.get(), 16); + vector_state->SetVectorType(0); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + matrix_state->set_mtype(0xFFFFFFFF); + mpact::sim::generic::Instruction inst(0, state.get()); + inst.AppendSource(new mpact::sim::generic::ImmediateOperand<uint32_t>(32)); + inst.AppendSource(new mpact::sim::generic::ImmediateOperand<uint32_t>(0)); + auto* reg = state->GetRegister<mpact::sim::riscv::RV32Register>("x1").first; + inst.AppendDestination(reg->CreateDestinationOperand(0)); + + mpact::sim::riscv::Vsetvl(/*rd_zero=*/false, /*rs1_zero=*/true, &inst); + EXPECT_EQ(matrix_state->mtype(), 0); +} + +TEST(RiscVZvtStateTest, VsetivliZeroesMtype) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + state->mstatus()->Set((state->mstatus()->GetUint32() & ~0x0600U) | (1U << 9)); + auto vector_state = + std::make_unique<mpact::sim::riscv::RiscVVectorState>(state.get(), 16); + vector_state->SetVectorType(0); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + matrix_state->set_mtype(0xFFFFFFFF); + mpact::sim::generic::Instruction inst(0, state.get()); + inst.AppendSource(new mpact::sim::generic::ImmediateOperand<uint32_t>(32)); + inst.AppendSource(new mpact::sim::generic::ImmediateOperand<uint32_t>(0)); + auto* reg = state->GetRegister<mpact::sim::riscv::RV32Register>("x0").first; + inst.AppendDestination(reg->CreateDestinationOperand(0)); + + mpact::sim::riscv::Vsetvl(/*rd_zero=*/true, /*rs1_zero=*/true, &inst); + EXPECT_EQ(matrix_state->mtype(), 0); +} + +TEST(RiscVZvtStateTest, MtypeCsrTrapWhenVsOff) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + bool trap_taken = false; + uint64_t trap_code = 0; + state->set_on_trap([&trap_taken, &trap_code]( + bool, uint64_t, uint64_t exception_code, uint64_t, + const mpact::sim::generic::Instruction*) { + trap_taken = true; + trap_code = exception_code; + return true; + }); + + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + auto* mstatus = state->mstatus(); + mstatus->Set(mstatus->GetUint32() & ~0x0600U); // VS bits [10:9] = 0 (Off) + + ASSERT_OK_AND_ASSIGN( + auto* mtype, + state->csr_set()->GetCsr(mpact::sim::riscv::kRiscVMtypeCsrIndex)); + + trap_taken = false; + mtype->AsUint32(); + EXPECT_TRUE(trap_taken); + EXPECT_EQ(trap_code, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction)); + + trap_taken = false; + mtype->AsUint64(); + EXPECT_TRUE(trap_taken); + EXPECT_EQ(trap_code, + static_cast<uint64_t>( + ::mpact::sim::riscv::ExceptionCode::kIllegalInstruction)); +} + +TEST(RiscVZvtStateTest, MtypeCsrSucceedsWhenMsOffAndVsOn) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + bool trap_taken = false; + state->set_on_trap([&trap_taken](bool, uint64_t, uint64_t, uint64_t, + const mpact::sim::generic::Instruction*) { + trap_taken = true; + return true; + }); + + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + auto* mstatus = state->mstatus(); + // VS bits [10:9] = 1 (Initial), MS bits [30:29] = 0 (Off). + mstatus->Set((mstatus->GetUint32() & ~0x6000'0600U) | (1U << 9)); + + ASSERT_OK_AND_ASSIGN( + auto* mtype, + state->csr_set()->GetCsr(mpact::sim::riscv::kRiscVMtypeCsrIndex)); + + trap_taken = false; + mtype->AsUint32(); + EXPECT_FALSE(trap_taken); + + mtype->AsUint64(); + EXPECT_FALSE(trap_taken); +} + +TEST(RiscVZvtStateTest, CreateValidatesTileElements) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + auto matrix_state_res = RiscVZvtMatrixState::Create(state.get(), 0); + EXPECT_FALSE(matrix_state_res.ok()); + EXPECT_EQ(matrix_state_res.status().code(), + absl::StatusCode::kInvalidArgument); + + auto matrix_state_neg_res = RiscVZvtMatrixState::Create(state.get(), -4); + EXPECT_FALSE(matrix_state_neg_res.ok()); + EXPECT_EQ(matrix_state_neg_res.status().code(), + absl::StatusCode::kInvalidArgument); + + // Non-power-of-two tile_elements >= 4 must be rejected. + auto matrix_state_non_pow2_res = RiscVZvtMatrixState::Create(state.get(), 6); + EXPECT_FALSE(matrix_state_non_pow2_res.ok()); + EXPECT_EQ(matrix_state_non_pow2_res.status().code(), + absl::StatusCode::kInvalidArgument); + + // Creating a second matrix state on the same RiscVState fails because the + // mtype CSR is already registered. + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + auto duplicate_res = RiscVZvtMatrixState::Create(state.get(), 32); + EXPECT_FALSE(duplicate_res.ok()); +} + +TEST(RiscVZvtStateTest, GetScratchDbAllocatesAndCaches) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + // Allocate a scratch DataBuffer of size 16. + auto* db16 = matrix_state->GetScratchDb(16); + ASSERT_NE(db16, nullptr); + EXPECT_EQ(db16->size<uint8_t>(), 16); + + // Requesting the same size again returns the cached DataBuffer. + EXPECT_EQ(matrix_state->GetScratchDb(16), db16); + + // Requesting a smaller size that has not been allocated yet allocates it + // without resizing scratch_dbs_. + auto* db8 = matrix_state->GetScratchDb(8); + ASSERT_NE(db8, nullptr); + EXPECT_EQ(db8->size<uint8_t>(), 8); + EXPECT_NE(db8, db16); +} + +TEST(RiscVZvtStateTest, GetAndSetElemAndInvalidWidthErrorPaths) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + + // Invalid tile element width in GetPunningOffset, SetElem, and GetElem. + EXPECT_EQ(matrix_state + ->GetPunningOffset(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/12) + .status() + .code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(matrix_state + ->SetElem<uint32_t>(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/12, /*val=*/42U) + .code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(matrix_state + ->GetElem<uint32_t>(/*tile=*/0, /*row=*/0, /*col=*/0, + /*tile_element_width=*/12) + .status() + .code(), + absl::StatusCode::kInvalidArgument); + + // Valid SetElem and GetElem round-trip. + ASSERT_OK(matrix_state->SetElem<uint32_t>( + /*tile=*/0, /*row=*/1, /*col=*/2, /*tile_element_width=*/32, + /*val=*/0xCAFEBABEU)); + ASSERT_OK_AND_ASSIGN(uint32_t val, matrix_state->GetElem<uint32_t>( + /*tile=*/0, /*row=*/1, /*col=*/2, + /*tile_element_width=*/32)); + EXPECT_EQ(val, 0xCAFEBABEU); +} + +TEST(RiscVZvtStateTest, GetMatrixStateLookup) { + FlatDemandMemory memory; + auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory); + mpact::sim::generic::Instruction inst(0, state.get()); + + // Before RiscVZvtMatrixState::Create, mtype CSR is not registered. + auto missing_res = mpact::sim::riscv::GetMatrixState(&inst); + EXPECT_FALSE(missing_res.ok()); + + ASSERT_OK_AND_ASSIGN(auto matrix_state, + RiscVZvtMatrixState::Create(state.get(), 32)); + ASSERT_OK_AND_ASSIGN(auto* found_state, + mpact::sim::riscv::GetMatrixState(&inst)); + EXPECT_EQ(found_state, matrix_state.get()); +} + +} // namespace