blob: d07b4615ee75efe48878282a4dca1417aad9048e [file]
// Copyright 2024 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 "mpact/sim/util/memory/single_initiator_router.h"
#include <cstdint>
#include <string>
#include "absl/container/btree_map.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "mpact/sim/util/memory/memory_interface.h"
#include "mpact/sim/util/memory/tagged_memory_interface.h"
// This file provides the method implementation of the SingleInitiatorRouter
// class.
namespace mpact {
namespace sim {
namespace util {
SingleInitiatorRouter::SingleInitiatorRouter(std::string name) : name_(name) {}
SingleInitiatorRouter::~SingleInitiatorRouter() {}
// This is a convenience helper function used to implement the three AddTarget
// methods, that add memory targets to the router, providing the
// memory interface, base address, and address space size.
template <typename Interface>
static absl::Status AddTargetPrivate(
SingleInitiatorRouter::InterfaceMap<Interface>& map, Interface* interface,
uint64_t base, uint64_t top) {
// Make sure the range is valid and makes sense.
if (base > top) {
return absl::InvalidArgumentError(
"Memory range base must be less than the top");
}
if (base == top) {
return absl::InvalidArgumentError("Memory range size must be non-zero");
}
// Make sure the range doesn't conflict with an existing range.
auto it = map.find({base, top});
if (it != map.end()) {
// If it is a duplicate, just ignore it and return ok.
if ((it->first.base == base) && (it->first.top == top)) {
return absl::OkStatus();
}
// Otherwise return an error.
return absl::AlreadyExistsError(absl::StrCat(
"Memory range conflicts with existing range: [",
absl::Hex(it->first.base), "..", absl::Hex(it->first.top), ">"));
}
map.emplace(SingleInitiatorRouter::AddressRange{base, top}, interface);
return absl::OkStatus();
}
// Template specializations of the AddTarget method.
template <>
absl::Status SingleInitiatorRouter::AddTarget<MemoryInterface>(
MemoryInterface* memory, uint64_t base, uint64_t top) {
return AddTargetPrivate<MemoryInterface>(memory_targets_, memory, base, top);
}
template <>
absl::Status SingleInitiatorRouter::AddTarget<TaggedMemoryInterface>(
TaggedMemoryInterface* tagged_memory, uint64_t base, uint64_t top) {
return AddTargetPrivate<TaggedMemoryInterface>(tagged_targets_, tagged_memory,
base, top);
}
template <>
absl::Status SingleInitiatorRouter::AddTarget<AtomicMemoryOpInterface>(
AtomicMemoryOpInterface* atomic_memory, uint64_t base, uint64_t top) {
return AddTargetPrivate<AtomicMemoryOpInterface>(atomic_targets_,
atomic_memory, base, top);
}
// Template specializations of the AddDefaultTarget method.
template <>
absl::Status SingleInitiatorRouter::AddDefaultTarget<MemoryInterface>(
MemoryInterface* memory) {
if ((memory != nullptr) && (default_memory_target_ != nullptr)) {
return absl::AlreadyExistsError("Default memory target already exists");
}
default_memory_target_ = memory;
return absl::OkStatus();
}
template <>
absl::Status SingleInitiatorRouter::AddDefaultTarget<TaggedMemoryInterface>(
TaggedMemoryInterface* tagged_memory) {
if ((tagged_memory != nullptr) && (default_tagged_target_ != nullptr)) {
return absl::AlreadyExistsError(
"Default tagged memory target already exists");
}
default_tagged_target_ = tagged_memory;
return absl::OkStatus();
}
template <>
absl::Status SingleInitiatorRouter::AddDefaultTarget<AtomicMemoryOpInterface>(
AtomicMemoryOpInterface* atomic_memory) {
if ((atomic_memory != nullptr) && (default_atomic_target_ != nullptr)) {
return absl::AlreadyExistsError(
"Default atomic memory target already exists");
}
default_atomic_target_ = atomic_memory;
return absl::OkStatus();
}
// The next methods are the overridden methods of the different memory
// interfaces. These perform lookups to find an appropriate target. Failing that
// they log an error and return.
// Plain memory load.
void SingleInitiatorRouter::Load(uint64_t address, DataBuffer* db,
Instruction* inst, ReferenceCount* context) {
int size = db->size<uint8_t>();
auto it = memory_targets_.find({address, address + size - 1});
if (it != memory_targets_.end()) {
auto& range = it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return it->second->Load(address, db, inst, context);
}
}
// Only use default if there was no match.
if ((it == memory_targets_.end()) && (default_memory_target_ != nullptr)) {
return default_memory_target_->Load(address, db, inst, context);
}
// Since the plain memory load can occur in both MemoryInterface and
// TaggedMemoryInterface, we need to check the tagged memory interface map
// too.
auto tagged_it = tagged_targets_.find({address, address});
if (tagged_it != tagged_targets_.end()) {
auto& range = tagged_it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return tagged_it->second->Load(address, db, inst, context);
}
}
// Only use default if there was no match.
if ((tagged_it == tagged_targets_.end()) &&
(default_tagged_target_ != nullptr)) {
return default_tagged_target_->Load(address, db, inst, context);
}
LOG(ERROR) << absl::StrCat("No target found for address: 0x",
absl::Hex(address));
}
// Vector memory load.
void SingleInitiatorRouter::Load(DataBuffer* address_db, DataBuffer* mask_db,
int el_size, DataBuffer* db, Instruction* inst,
ReferenceCount* context) {
// This is a vector load, so check each address that is not masked off.
// Vector memory loads will not be split across multiple targets.
int count = address_db->size<uint64_t>();
MemoryInterface* memory = nullptr;
for (int i = 0; i < count; i++) {
// If the mask is true, check this address.
if (mask_db->Get<uint8_t>(i)) {
auto address = address_db->Get<uint64_t>(i);
auto it = memory_targets_.find({address, address + el_size - 1});
MemoryInterface* tmp_memory = nullptr;
if (it == memory_targets_.end()) {
// If there are no overlapping targets, use the default, if it is set.
if (default_memory_target_ == nullptr) break;
tmp_memory = default_memory_target_;
} else {
auto& range = it->first;
// If there is no proper overlap, just break out of the loop. We are
// not splitting the memory access across multiple targets.
if ((range.base > address) || (range.top < address + el_size - 1))
break;
tmp_memory = it->second;
}
if (memory != nullptr) {
if (tmp_memory != memory) {
LOG(ERROR) << "Multiple targets found for address vector load";
return;
}
} else {
memory = tmp_memory;
}
}
}
if (memory != nullptr) {
return memory->Load(address_db, mask_db, el_size, db, inst, context);
}
// Since the vector memory load can occur in both MemoryInterface and
// TaggedMemoryInterface, we need to check the tagged memory interface map
// too.
TaggedMemoryInterface* tagged_memory = nullptr;
for (int i = 0; i < count; i++) {
if (mask_db->Get<uint8_t>(i)) {
auto address = address_db->Get<uint64_t>(i);
auto it = tagged_targets_.find({address, address + el_size - 1});
TaggedMemoryInterface* tmp_memory = nullptr;
if (it == tagged_targets_.end()) {
// If there are no overlapping targets, use the default, if it is set.
if (default_tagged_target_ == nullptr) break;
tmp_memory = default_tagged_target_;
} else {
auto& range = it->first;
// If there is no proper overlap, just break out of the loop. We are
// not splitting the memory access across multiple targets.
if ((range.base > address) || (range.top < address + el_size - 1))
break;
tmp_memory = it->second;
}
if (tagged_memory != nullptr) {
if (tmp_memory != tagged_memory) {
LOG(ERROR) << "Multiple targets found for address vector load";
return;
}
} else {
tagged_memory = tmp_memory;
}
}
}
if (tagged_memory != nullptr) {
return tagged_memory->Load(address_db, mask_db, el_size, db, inst, context);
}
LOG(ERROR) << "No target found for vector load";
}
// Tagged memory load.
void SingleInitiatorRouter::Load(uint64_t address, DataBuffer* db,
DataBuffer* tags, Instruction* inst,
ReferenceCount* context) {
int size;
// If db is null, then this is a tag load. The size for routing purposes is
// the size of tags * 8.
if (db == nullptr) {
size = tags->size<uint8_t>() << 3;
} else {
size = db->size<uint8_t>();
}
auto tagged_it = tagged_targets_.find({address, address + size - 1});
if (tagged_it != tagged_targets_.end()) {
auto& range = tagged_it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return tagged_it->second->Load(address, db, tags, inst, context);
}
}
// Only use default if there was no match.
if ((tagged_it == tagged_targets_.end()) &&
(default_tagged_target_ != nullptr)) {
return default_tagged_target_->Load(address, db, tags, inst, context);
}
LOG(ERROR) << absl::StrCat("No target found for address: 0x",
absl::Hex(address));
}
// Plain memory store.
void SingleInitiatorRouter::Store(uint64_t address, DataBuffer* db) {
int size = db->size<uint8_t>();
auto it = memory_targets_.find({address, address + size});
if (it != memory_targets_.end()) {
auto& range = it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return it->second->Store(address, db);
}
}
// Only use default if there was no match.
if ((it == memory_targets_.end()) && (default_memory_target_ != nullptr)) {
return default_memory_target_->Store(address, db);
}
// Since the plain memory store can occur in both MemoryInterface and
// TaggedMemoryInterface, we need to check the tagged memory interface map
// too.
auto tagged_it = tagged_targets_.find({address, address});
if (tagged_it != tagged_targets_.end()) {
auto& range = tagged_it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return tagged_it->second->Store(address, db);
}
}
// Only use default if there was no match.
if ((tagged_it == tagged_targets_.end()) &&
(default_tagged_target_ != nullptr)) {
return default_tagged_target_->Store(address, db);
}
LOG(ERROR) << absl::StrCat("No target found for address: 0x",
absl::Hex(address));
}
// Vector memory store.
void SingleInitiatorRouter::Store(DataBuffer* address_db, DataBuffer* mask_db,
int el_size, DataBuffer* db) {
// This is a vector store, so check each address that is not masked off.
// Vector memory stores will not be split across multiple targets.
int count = address_db->size<uint64_t>();
MemoryInterface* memory = nullptr;
for (int i = 0; i < count; i++) {
// If the mask is true, check this address.
if (mask_db->Get<uint8_t>(i)) {
auto address = address_db->Get<uint64_t>(i);
auto it = memory_targets_.find({address, address + el_size - 1});
MemoryInterface* tmp_memory = nullptr;
if (it == memory_targets_.end()) {
// If there are no overlapping targets, use the default, if it is set.
if (default_memory_target_ == nullptr) break;
tmp_memory = default_memory_target_;
} else {
auto& range = it->first;
// If there is no proper overlap, just break out of the loop. We are
// not splitting the memory access across multiple targets.
if ((range.base > address) || (range.top < address + el_size - 1))
break;
tmp_memory = it->second;
}
if (memory != nullptr) {
if (tmp_memory != memory) {
LOG(ERROR) << "Multiple targets found for address vector load";
return;
}
} else {
memory = tmp_memory;
}
}
}
if (memory != nullptr) {
return memory->Store(address_db, mask_db, el_size, db);
}
// Since the vector memory store can occur in both MemoryInterface and
// TaggedMemoryInterface, we need to check the tagged memory interface map
// too.
TaggedMemoryInterface* tagged_memory = nullptr;
for (int i = 0; i < count; i++) {
if (mask_db->Get<uint8_t>(i)) {
auto address = address_db->Get<uint64_t>(i);
auto it = tagged_targets_.find({address, address + el_size});
TaggedMemoryInterface* tmp_memory = nullptr;
if (it == tagged_targets_.end()) {
// If there are no overlapping targets, use the default, if it is set.
if (default_tagged_target_ == nullptr) break;
tmp_memory = default_tagged_target_;
} else {
auto& range = it->first;
// If there is no proper overlap, just break out of the loop. We are
// not splitting the memory access across multiple targets.
if ((range.base > address) || (range.top < address + el_size)) break;
tmp_memory = it->second;
}
if (tagged_memory != nullptr) {
if (tmp_memory != tagged_memory) {
LOG(ERROR) << "Multiple targets found for address vector load";
return;
}
} else {
tagged_memory = tmp_memory;
}
}
}
if (tagged_memory != nullptr) {
return tagged_memory->Store(address_db, mask_db, el_size, db);
}
LOG(ERROR) << "No target found for vector store";
}
// Simple tagged memory store.
void SingleInitiatorRouter::Store(uint64_t address, DataBuffer* db,
DataBuffer* tags) {
int size = db->size<uint8_t>();
auto tagged_it = tagged_targets_.find({address, address + size - 1});
if (tagged_it != tagged_targets_.end()) {
auto& range = tagged_it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return tagged_it->second->Store(address, db, tags);
} else {
LOG(ERROR) << absl::StrCat("No target found for address: 0x",
absl::Hex(address));
}
}
// Only use default if there was no match.
if ((tagged_it == tagged_targets_.end()) &&
(default_tagged_target_ != nullptr)) {
return default_tagged_target_->Store(address, db, tags);
}
LOG(ERROR) << absl::StrCat("No target found for address: 0x",
absl::Hex(address));
}
// Atomic memory operation.
absl::Status SingleInitiatorRouter::PerformMemoryOp(uint64_t address,
Operation op,
DataBuffer* db,
Instruction* inst,
ReferenceCount* context) {
int size = db->size<uint8_t>();
auto atomic_it = atomic_targets_.find({address, address + size - 1});
if (atomic_it != atomic_targets_.end()) {
auto& range = atomic_it->first;
if ((range.base <= address) && (range.top >= address + size - 1)) {
return atomic_it->second->PerformMemoryOp(address, op, db, inst, context);
}
}
// Only use default if there was no match.
if ((atomic_it == atomic_targets_.end()) &&
(default_atomic_target_ != nullptr)) {
return default_atomic_target_->PerformMemoryOp(address, op, db, inst,
context);
}
return absl::NotFoundError(
absl::StrCat("No target found for address: ", absl::Hex(address)));
}
} // namespace util
} // namespace sim
} // namespace mpact