blob: c56a31e03a7a61ddae0a1d7ac47c969ffad79fd8 [file]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "mpact/sim/generic/arch_state.h"
#include <any>
#include <cstdint>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "googlemock/include/gmock/gmock.h"
#include "googletest/include/gtest/gtest.h"
#include "mpact/sim/generic/delay_line.h"
#include "mpact/sim/generic/delay_line_interface.h"
#include "mpact/sim/generic/fifo.h"
#include "mpact/sim/generic/operand_interface.h"
#include "mpact/sim/generic/register.h"
namespace mpact {
namespace sim {
namespace generic {
namespace {
using ::testing::Ne;
using ::testing::StrEq;
using ScalarFifo = Fifo<uint32_t>;
using ScalarRegister = Register<uint32_t>;
using Vector8Register = VectorRegister<uint32_t, 8>;
constexpr int kFifoDepth = 3;
constexpr char kRegName1[] = "Reg1";
constexpr char kRegName2[] = "Reg2";
constexpr char kFifoName1[] = "Fifo1";
constexpr char kFifoName2[] = "Fifo2";
// Define a new delay line type.
struct IntDelayRecord {
int *destination;
int value;
void Apply() { *destination = value; }
IntDelayRecord() = delete;
IntDelayRecord(int *dest, int val) : destination(dest), value(val) {}
};
using IntDelayLine = DelayLine<IntDelayRecord>;
// Define a class that maintains program counter without being a register.
// This is so simplify maintaining a scalar value that changes quickly.
class MyProgramCounter : public SourceOperandInterface {
public:
MyProgramCounter() = default;
uint32_t pc() const { return pc_; }
void set_pc(uint32_t val) { pc_ = val; }
// Methods for accessing the nth value element.
bool AsBool(int index) override { return static_cast<bool>(pc_); }
int8_t AsInt8(int index) override { return static_cast<int8_t>(pc_); }
uint8_t AsUint8(int index) override { return static_cast<uint8_t>(pc_); }
int16_t AsInt16(int index) override { return static_cast<int16_t>(pc_); }
uint16_t AsUint16(int) override { return static_cast<uint16_t>(pc_); }
int32_t AsInt32(int index) override { return static_cast<int32_t>(pc_); }
uint32_t AsUint32(int index) override { return static_cast<uint32_t>(pc_); }
int64_t AsInt64(int index) override { return static_cast<int64_t>(pc_); }
uint64_t AsUint64(int index) override { return static_cast<uint64_t>(pc_); }
// Return a pointer to the object instance that implmements the state in
// question (or nullptr) if no such object "makes sense". This is used if
// the object requires additional manipulation - such as a fifo that needs
// to be pop'ed. If no such manipulation is required, nullptr should be
// returned.
std::any GetObject() const override { return nullptr; }
// Returns the shape of the operand {}
std::vector<int> shape() const override { return shape_; }
std::string AsString() const override { return std::string("PC"); }
private:
uint32_t pc_;
std::vector<int> shape_;
};
// Define a class that derives from ArchState since constructors are
// protected.
class MyArchState : public ArchState {
public:
MyArchState(absl::string_view id, SourceOperandInterface *pc_op)
: ArchState(id, pc_op) {}
explicit MyArchState(absl::string_view id) : MyArchState(id, nullptr) {}
};
// Test fixture class that sets up the ArchState derived class and registers
// some registers, a fifo, and a pc source operand.
class ArchStateTest : public testing::Test {
protected:
ArchStateTest() : my_pc_() {
arch_state_ = new MyArchState("TestArchitecture", &my_pc_);
for (int reg_no = 0; reg_no < 16; reg_no++) {
arch_state_->AddRegister<ScalarRegister>(absl::StrCat("R", reg_no));
}
for (int reg_no = 0; reg_no < 16; reg_no++) {
arch_state_->AddRegister<Vector8Register>(absl::StrCat("V", reg_no));
}
arch_state_->AddFifo<ScalarFifo>("F0", kFifoDepth);
}
~ArchStateTest() override { delete arch_state_; }
MyProgramCounter my_pc_;
ArchState *arch_state_;
};
// Test that the ArchState instance is properly initialized.
TEST_F(ArchStateTest, BasicProperties) {
EXPECT_EQ(arch_state_->id().compare("TestArchitecture"), 0);
EXPECT_NE(arch_state_->program_error_controller(), nullptr);
EXPECT_THAT(arch_state_->program_error_controller()->name(),
StrEq("TestArchitectureErrors"));
EXPECT_EQ(arch_state_->pc_operand(),
static_cast<SourceOperandInterface *>(&my_pc_));
for (int reg_no = 0; reg_no < 16; reg_no++) {
EXPECT_THAT(arch_state_->registers()->find(absl::StrCat("R", reg_no)),
Ne(arch_state_->registers()->end()));
EXPECT_THAT(arch_state_->registers()->find(absl::StrCat("V", reg_no)),
Ne(arch_state_->registers()->end()));
}
EXPECT_THAT(arch_state_->fifos()->find("F0"),
Ne(arch_state_->fifos()->end()));
EXPECT_EQ(arch_state_->registers()->find("X0"),
arch_state_->registers()->end());
EXPECT_EQ(arch_state_->fifos()->find("X0"), arch_state_->fifos()->end());
}
TEST_F(ArchStateTest, EmptyDelayLineAdvance) {
arch_state_->AdvanceDelayLines();
}
// Test that the function delay line works and get advanced by ArchState.
TEST_F(ArchStateTest, FunctionDelayLine) {
int my_value = 0;
arch_state_->function_delay_line()->Add(1, [&]() { my_value = 1; });
arch_state_->function_delay_line()->Add(2, [&]() { my_value = 2; });
EXPECT_EQ(my_value, 0);
arch_state_->AdvanceDelayLines();
EXPECT_EQ(my_value, 1);
arch_state_->AdvanceDelayLines();
EXPECT_EQ(my_value, 2);
}
// Test creation of a new delay line and that it gets advanced by ArchState.
TEST_F(ArchStateTest, AddDelayLine) {
IntDelayLine *int_delay_line =
arch_state_->CreateAndAddDelayLine<IntDelayLine>();
int my_value = 0;
int_delay_line->Add(1, &my_value, 1);
int_delay_line->Add(2, &my_value, 2);
EXPECT_EQ(my_value, 0);
arch_state_->AdvanceDelayLines();
EXPECT_EQ(my_value, 1);
arch_state_->AdvanceDelayLines();
EXPECT_EQ(my_value, 2);
}
// Test that the pc source operand is properly tied to the pc object.
TEST_F(ArchStateTest, PcOperand) {
my_pc_.set_pc(0xDEADBEEF);
EXPECT_EQ(arch_state_->pc_operand()->AsUint32(0), 0xDEADBEEF);
my_pc_.set_pc(0xA5A5A5A5);
EXPECT_EQ(arch_state_->pc_operand()->AsUint32(0), 0xA5A5A5A5);
}
// Test alternate ways to add registers.
TEST_F(ArchStateTest, AddRegister) {
auto *reg = new ScalarRegister(arch_state_, kRegName1);
arch_state_->AddRegister(reg);
// Also add the register with an alias.
arch_state_->AddRegister(kRegName2, reg);
auto iter = arch_state_->registers()->find(kRegName1);
auto *reg1 =
(iter == arch_state_->registers()->end()) ? nullptr : iter->second;
iter = arch_state_->registers()->find(kRegName2);
auto *reg2 =
(iter == arch_state_->registers()->end()) ? nullptr : iter->second;
EXPECT_EQ(reg1, reg);
EXPECT_EQ(reg2, reg);
arch_state_->RemoveRegister(kRegName2);
iter = arch_state_->registers()->find(kRegName1);
reg1 = (iter == arch_state_->registers()->end()) ? nullptr : iter->second;
iter = arch_state_->registers()->find(kRegName2);
reg2 = (iter == arch_state_->registers()->end()) ? nullptr : iter->second;
EXPECT_EQ(reg1, reg);
EXPECT_EQ(reg2, nullptr);
}
// Test alternate ways to add registers.
TEST_F(ArchStateTest, AddFifo) {
auto *fifo = new ScalarFifo(arch_state_, kFifoName1, 8);
arch_state_->AddFifo(fifo);
// Add fifo with an alias.
arch_state_->AddFifo(kFifoName2, fifo);
auto iter = arch_state_->fifos()->find(kFifoName1);
auto *fifo1 = (iter == arch_state_->fifos()->end()) ? nullptr : iter->second;
iter = arch_state_->fifos()->find(kFifoName2);
auto *fifo2 = (iter == arch_state_->fifos()->end()) ? nullptr : iter->second;
EXPECT_EQ(fifo1, fifo);
EXPECT_EQ(fifo2, fifo);
arch_state_->RemoveFifo(kFifoName2);
iter = arch_state_->fifos()->find(kFifoName1);
fifo1 = (iter == arch_state_->fifos()->end()) ? nullptr : iter->second;
iter = arch_state_->fifos()->find(kFifoName2);
fifo2 = (iter == arch_state_->fifos()->end()) ? nullptr : iter->second;
EXPECT_EQ(fifo1, fifo);
EXPECT_EQ(fifo2, nullptr);
}
} // namespace
} // namespace generic
} // namespace sim
} // namespace mpact