blob: 4dfc74be1e8ced40e2f8cb1f006243317934ef34 [file]
// 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_instructions.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include "absl/base/casts.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"
#include "riscv/riscv_register.h"
#include "riscv/riscv_state.h"
#include "riscv/riscv_vector_state.h"
#include "riscv/riscv_zvt_state.h"
namespace mpact::sim::riscv {
using ::mpact::sim::generic::DataBuffer;
using ::mpact::sim::generic::GetInstructionSource;
using ::mpact::sim::generic::operator*; // NOLINT
using ::mpact::sim::riscv::RiscVVectorState;
using ::mpact::sim::riscv::RV32VectorDestinationOperand;
using ::mpact::sim::riscv::RV32VectorSourceOperand;
namespace {
// ELEN for the Zve32{x,f} kBase.
constexpr int kElen = 32;
// Decoded kTile subset specifier (TSS). Spec 15.1.1.5.
struct Tss {
int kTile; // bits[30:27]
int pattern; // bits[26:24]: 0 = row, 1 = col.
uint32_t index; // bits[23:0].
};
Tss DecodeTss(uint32_t tss) {
return Tss{
.kTile = static_cast<int>((tss >> 27) & 0xF),
.pattern = static_cast<int>((tss >> 24) & 0x7),
.index = tss & 0xFFFFFF,
};
}
constexpr int kTssPatternCol = 1; // pattern 0 = row (the default branch).
// Reinterprets the low 16 bits of a bf16 value as the high half of an fp32.
float Bf16ToF32(uint16_t bits) {
uint32_t u = static_cast<uint32_t>(bits) << 16;
return absl::bit_cast<float>(u);
}
// Reads kTile element (row,col) of `kTile` at width `tile_element_width` as a
// generic 64-bit container, and the inverse. Used by the move/load/store paths
// which carry the element width dynamically.
uint64_t LoadTileBits(RiscVZvtMatrixState* matrix_state, int kTile, int row,
int col, int tile_element_width) {
const int kEffectiveTileEdge = tile_element_width < 64
? matrix_state->tile_elements()
: matrix_state->tile_elements() / 2;
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 load_elem(matrix_state->GetElem<uint8_t>(kTile, row, col, 8));
case 16:
return load_elem(matrix_state->GetElem<uint16_t>(kTile, row, col, 16));
case 32:
return load_elem(matrix_state->GetElem<uint32_t>(kTile, row, col, 32));
case 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;
}
}
void StoreTileBits(RiscVZvtMatrixState* matrix_state, int kTile, int row,
int col, int tile_element_width, uint64_t value) {
const int kEffectiveTileEdge = tile_element_width < 64
? matrix_state->tile_elements()
: matrix_state->tile_elements() / 2;
if (row < 0 || row >= kEffectiveTileEdge || col < 0 ||
col >= kEffectiveTileEdge) {
return;
}
absl::Status status;
switch (tile_element_width) {
case 8:
status = matrix_state->SetElem<uint8_t>(kTile, row, col, 8,
static_cast<uint8_t>(value));
break;
case 16:
status = matrix_state->SetElem<uint16_t>(kTile, row, col, 16,
static_cast<uint16_t>(value));
break;
case 32:
status = matrix_state->SetElem<uint32_t>(kTile, row, col, 32,
static_cast<uint32_t>(value));
break;
case 64:
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;
}
}
// Writes a 32-bit value to the scalar destination operand at `dest_index`.
void WriteScalar(const Instruction* inst, int dest_index, uint32_t value) {
auto* dest = inst->Destination(dest_index);
DataBuffer* db = dest->AllocateDataBuffer();
db->Set<uint32_t>(0, value);
db->Submit();
}
} // namespace
void RiscVZvtMsetMtype(const Instruction* inst) {
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const uint32_t kRs1 = GetInstructionSource<uint32_t>(inst, 0); // rs1
const uint32_t kRs2 = GetInstructionSource<uint32_t>(inst, 1); // rs2
// Spec 15.1.1.4: vl = 0; mtype = rs1; then vtype = rs2 as if by vsetvl.
vector_state->set_vector_length(0);
matrix_state->set_mtype(kRs1);
vector_state->SetVectorType(kRs2);
if (matrix_state->mtwiden() == 0) {
matrix_state->set_mtype(0);
return;
}
const int kSewBits = vector_state->selected_element_width() * 8;
const int kTwiden = matrix_state->twiden();
const int kTileElementWidth = kSewBits * kTwiden;
const int kEffectiveTileElements = kTileElementWidth < 64
? matrix_state->tile_elements()
: matrix_state->tile_elements() / 2;
const int kmax = RiscVZvtKmax(kSewBits);
const int kLmulElements =
vector_state->max_vector_length(); // LMUL * (VLEN/SEW).
// If the matrix unit is configured, force vma/vta = 1.
// TODO: also derive vtype.vlmul per the spec's min() formula; deferred while
// the v0.3 encoding firms up.
vector_state->SetVectorType(vector_state->vtype() | (1u << 6) | (1u << 7));
// kVill if the resulting TILE_ELEMENT_WIDTH is unsupported for the kBase
// (SEW*TWIDEN > ELEN).
const bool kVill = kTileElementWidth > kElen;
if (kVill) {
vector_state->set_vector_exception();
matrix_state->set_mtype(0);
return;
}
// kTileK = min(kTileK, KMAX); kTileM = min(kTileM, LMUL*EVE,
// EFFECTIVE_TILE_EDGE).
matrix_state->set_tile_k(std::min(matrix_state->tile_k(), kmax));
matrix_state->set_tile_m(std::min(
{matrix_state->tile_m(), kLmulElements, kEffectiveTileElements}));
}
void RiscVZvtMsetTn(const Instruction* inst) {
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const uint32_t kRs1 = GetInstructionSource<uint32_t>(inst, 0);
uint32_t kTileN;
if (matrix_state->mtwiden() == 0) {
// Degenerates to vsetvl using the current vtype: vl = min(rs1, VLMAX).
kTileN = std::min<uint32_t>(kRs1, vector_state->max_vector_length());
} else {
const int kBound = std::min(vector_state->max_vector_length(),
matrix_state->effective_tile_edge());
kTileN = std::min<uint32_t>(kRs1, kBound);
}
vector_state->set_vector_length(kTileN);
WriteScalar(inst, 0, kTileN);
}
void RiscVZvtMsetTm(const Instruction* inst) {
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const uint32_t kRs1 = GetInstructionSource<uint32_t>(inst, 0);
uint32_t kTileM;
if (matrix_state->mtwiden() == 0) {
kTileM = 0;
} else {
const int kBound = std::min(vector_state->max_vector_length(),
matrix_state->effective_tile_edge());
kTileM = std::min<uint32_t>(kRs1, kBound);
}
matrix_state->set_tile_m(kTileM);
WriteScalar(inst, 0, kTileM);
}
void RiscVZvtMsetTk(const Instruction* inst) {
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const uint32_t kRs1 = GetInstructionSource<uint32_t>(inst, 0);
uint32_t kTileK;
if (matrix_state->mtwiden() == 0) {
kTileK = 0;
} else {
const int kSewBits = vector_state->selected_element_width() * 8;
kTileK = std::min<uint32_t>(kRs1, RiscVZvtKmax(kSewBits));
}
matrix_state->set_tile_k(kTileK);
WriteScalar(inst, 0, kTileK);
}
// ----------------------------------------------------------------------------
// Tile manipulation
// ----------------------------------------------------------------------------
void RiscVZvtVtZero(const Instruction* inst) {
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const int kTile = GetInstructionSource<uint32_t>(inst, 0); // mtd
const int kTileElementWidth = matrix_state->tile_element_width_bits();
const int kTileM = matrix_state->tile_m();
const int kEffectiveTileEdge = kTileElementWidth < 64
? matrix_state->tile_elements()
: matrix_state->tile_elements() / 2;
const int kTileN =
std::min(vector_state->vector_length(), kEffectiveTileEdge);
for (int row = 0; row < kTileM; row++) {
for (int col = 0; col < kTileN; col++) {
StoreTileBits(matrix_state, kTile, row, col, kTileElementWidth, 0);
}
}
}
void RiscVZvtVtDiscard(const Instruction* /*inst*/) {
// Architecturally hints the kTile state is dead. No-op in the ISS.
}
// Number of kTile-subset elements processed: min(vl, EFFECTIVE_TILE_EDGE) for
// the given TILE_ELEMENT_WIDTH.
static int TileSubsetCount(RiscVZvtMatrixState* matrix_state,
RiscVVectorState* vector_state,
int tile_element_width) {
const int kEffectiveTileEdge = tile_element_width < 64
? matrix_state->tile_elements()
: matrix_state->tile_elements() / 2;
return std::min(vector_state->vector_length(), kEffectiveTileEdge);
}
void RiscVZvtVtmvVt(const Instruction* inst) {
// Tile row/col subset -> vector register group. Move TILE_ELEMENT_WIDTH =
// SEW.
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const Tss kTss = DecodeTss(GetInstructionSource<uint32_t>(inst, 0));
const int kSewBytes = vector_state->selected_element_width();
const int kTileElementWidth = kSewBytes * 8;
const int kCount =
TileSubsetCount(matrix_state, vector_state, kTileElementWidth);
const int kElementsPerVector =
vector_state->vector_register_byte_length() / kSewBytes;
auto* vd = static_cast<RV32VectorDestinationOperand*>(inst->Destination(0));
const int kNumRegs = (kCount + kElementsPerVector - 1) / kElementsPerVector;
for (int r = 0; r < kNumRegs; r++) {
DataBuffer* db = vd->AllocateDataBuffer(r);
absl::Span<uint8_t> bytes = db->Get<uint8_t>();
std::memset(bytes.data(), 0, bytes.size()); // tail-agnostic fill.
for (int kE = 0; kE < kElementsPerVector; kE++) {
const int kElem = r * kElementsPerVector + kE;
if (kElem >= kCount) break;
const uint64_t kVal =
(kTss.pattern == kTssPatternCol)
? LoadTileBits(matrix_state, kTss.kTile, kElem, kTss.index,
kTileElementWidth)
: LoadTileBits(matrix_state, kTss.kTile, kTss.index, kElem,
kTileElementWidth);
std::memcpy(&bytes[kE * kSewBytes], &kVal, kSewBytes);
}
db->Submit();
}
}
void RiscVZvtVtmvTv(const Instruction* inst) {
// Vector register group -> kTile row/col subset. Move TILE_ELEMENT_WIDTH =
// SEW.
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
auto* vs = static_cast<RV32VectorSourceOperand*>(inst->Source(0));
const Tss kTss = DecodeTss(GetInstructionSource<uint32_t>(inst, 1));
const int kSewBytes = vector_state->selected_element_width();
const int kTileElementWidth = kSewBytes * 8;
const int kCount =
TileSubsetCount(matrix_state, vector_state, kTileElementWidth);
const int kElementsPerVector =
vector_state->vector_register_byte_length() / kSewBytes;
for (int kElem = 0; kElem < kCount; kElem++) {
const int kReg = kElem / kElementsPerVector;
const int kE = kElem % kElementsPerVector;
auto bytes = vs->GetRegister(kReg)->data_buffer()->Get<uint8_t>();
uint64_t kVal = 0;
std::memcpy(&kVal, &bytes[kE * kSewBytes], kSewBytes);
if (kTss.pattern == kTssPatternCol) {
StoreTileBits(matrix_state, kTss.kTile, kElem, kTss.index,
kTileElementWidth, kVal);
} else {
StoreTileBits(matrix_state, kTss.kTile, kTss.index, kElem,
kTileElementWidth, kVal);
}
}
}
void RiscVZvtVtle(int eew_bits, const Instruction* inst) {
// Memory -> kTile row/col subset. EEW is statically encoded.
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* state = matrix_state->riscv_state();
auto* vector_state = state->rv_vector();
const uint64_t kBase = GetInstructionSource<uint64_t>(inst, 0); // rs1
const Tss kTss = DecodeTss(GetInstructionSource<uint32_t>(inst, 1)); // rs2
const int kElementSize = eew_bits / 8;
const int kCount = TileSubsetCount(matrix_state, vector_state, eew_bits);
DataBuffer* db = matrix_state->GetScratchDb(kCount * kElementSize);
state->memory()->Load(kBase, db, nullptr, nullptr);
absl::Span<uint8_t> bytes = db->Get<uint8_t>();
for (int kE = 0; kE < kCount; kE++) {
uint64_t kVal = 0;
std::memcpy(&kVal, &bytes[kE * kElementSize], kElementSize);
if (kTss.pattern == kTssPatternCol) {
StoreTileBits(matrix_state, kTss.kTile, kE, kTss.index, eew_bits, kVal);
} else {
StoreTileBits(matrix_state, kTss.kTile, kTss.index, kE, eew_bits, kVal);
}
}
}
void RiscVZvtVtse(int eew_bits, const Instruction* inst) {
// Tile row/col subset -> memory. EEW is statically encoded.
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* state = matrix_state->riscv_state();
auto* vector_state = state->rv_vector();
const uint64_t kBase = GetInstructionSource<uint64_t>(inst, 0); // rs1
const Tss kTss = DecodeTss(GetInstructionSource<uint32_t>(inst, 1)); // rs2
const int kElementSize = eew_bits / 8;
const int kCount = TileSubsetCount(matrix_state, vector_state, eew_bits);
DataBuffer* db = matrix_state->GetScratchDb(kCount * kElementSize);
absl::Span<uint8_t> bytes = db->Get<uint8_t>();
for (int kE = 0; kE < kCount; kE++) {
const uint64_t kVal =
(kTss.pattern == kTssPatternCol)
? LoadTileBits(matrix_state, kTss.kTile, kE, kTss.index, eew_bits)
: LoadTileBits(matrix_state, kTss.kTile, kTss.index, kE, eew_bits);
std::memcpy(&bytes[kE * kElementSize], &kVal, kElementSize);
}
state->memory()->Store(kBase, db);
}
// ----------------------------------------------------------------------------
// Matrix multiply: C[m,n] += sum_k A[k,m] * B[k,n]
// ----------------------------------------------------------------------------
void RiscVZvtMatmul(RiscVZvtMatmulKind kind, const Instruction* inst) {
auto m_result = GetMatrixState(inst);
auto* matrix_state = *m_result;
auto* vector_state = matrix_state->riscv_state()->rv_vector();
const int kSewBytes = vector_state->selected_element_width();
const int kSewBits = kSewBytes * 8;
const int kElementsPerVector =
vector_state->vector_register_byte_length() / kSewBytes; // elems/kReg.
const int kStride = 8 / RiscVZvtKmax(kSewBits); // regs between rows.
const int kTileK =
matrix_state->tile_k(); // K: the inner dimension of the dot product.
const int kTileM =
matrix_state
->tile_m(); // M: the number of rows of the accumulator kTile.
const int kDestTile = GetInstructionSource<uint32_t>(inst, 2); // mtd
const int kTileElementWidth =
matrix_state
->tile_element_width_bits(); // == 32 for the supported datatypes.
const int kEffectiveTileEdge = kTileElementWidth < 64
? matrix_state->tile_elements()
: matrix_state->tile_elements() / 2;
// N: the number of columns of the accumulator kTile (derived from vector
// length).
const int kTileN =
std::min(vector_state->vector_length(), kEffectiveTileEdge);
auto* a = static_cast<RV32VectorSourceOperand*>(inst->Source(0));
auto* b = static_cast<RV32VectorSourceOperand*>(inst->Source(1));
absl::Span<uint8_t> a_spans[32] = {};
absl::Span<uint8_t> b_spans[32] = {};
const bool kIsFp =
(kind == RiscVZvtMatmulKind::kFp32 || kind == RiscVZvtMatmulKind::kBf16);
// Reads element `col` of row `kk` from a vector-group operand.
auto read_fp = [&](RV32VectorSourceOperand* op, absl::Span<uint8_t>* spans,
int kk, int col) -> float {
const int kReg = kk * kStride + col / kElementsPerVector;
const int kE = col % kElementsPerVector;
if (spans[kReg].empty()) {
spans[kReg] = op->GetRegister(kReg)->data_buffer()->Get<uint8_t>();
}
absl::Span<uint8_t> span = spans[kReg];
if (kind == RiscVZvtMatmulKind::kBf16) {
uint16_t u;
std::memcpy(&u, &span[kE * kSewBytes], sizeof(u));
return Bf16ToF32(u);
}
float f;
std::memcpy(&f, &span[kE * kSewBytes], sizeof(f));
return f;
};
auto read_int = [&](RV32VectorSourceOperand* op, absl::Span<uint8_t>* spans,
int kk, int col) -> int32_t {
const int kReg = kk * kStride + col / kElementsPerVector;
const int kE = col % kElementsPerVector;
if (spans[kReg].empty()) {
spans[kReg] = op->GetRegister(kReg)->data_buffer()->Get<uint8_t>();
}
absl::Span<uint8_t> span = spans[kReg];
if (kind == RiscVZvtMatmulKind::kInt8Signed) {
return static_cast<int8_t>(span[kE]);
}
return static_cast<uint8_t>(span[kE]);
};
for (int mm = 0; mm < kTileM; mm++) {
for (int nn = 0; nn < kTileN; nn++) {
if (kIsFp) {
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);
}
// SetElem cannot fail because GetElem with the same parameters
// succeeded above.
matrix_state->SetElem<float>(kDestTile, mm, nn, kTileElementWidth, acc)
.IgnoreError();
} else {
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);
}
// SetElem cannot fail because GetElem with the same parameters
// succeeded above.
matrix_state
->SetElem<int32_t>(kDestTile, mm, nn, kTileElementWidth, acc)
.IgnoreError();
}
}
}
}
} // namespace mpact::sim::riscv