blob: dc71a23ac6bfb7600d8bc64857992ea2010af52e [file] [edit]
// 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