blob: 5deed7bc03d2b3a70ead89b6d2ebefe022b8f7a1 [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/fifo.h"
#include <cstdint>
#include <memory>
#include "absl/memory/memory.h"
#include "googlemock/include/gmock/gmock.h"
#include "googletest/include/gtest/gtest.h"
#include "mpact/sim/generic/config.h"
#include "mpact/sim/generic/data_buffer.h"
#include "mpact/sim/generic/program_error.h"
#include "mpact/sim/proto/component_data.pb.h"
#include "src/google/protobuf/text_format.h"
#include "src/google/protobuf/util/message_differencer.h"
namespace mpact {
namespace sim {
namespace generic {
namespace {
constexpr char kControllerName[] = "ErrorController";
constexpr char kOverflowName[] = "FifoOverflow";
constexpr char kUnderflowName[] = "FifoUnderflow";
constexpr int kVectorLength = 8;
constexpr int kMatrixRows = 8;
constexpr int kMatrixCols = 16;
constexpr int kFifoDepth = 3;
using testing::StrEq;
using ScalarFifo = Fifo<uint32_t>;
using Vector8Fifo = VectorFifo<uint32_t, kVectorLength>;
using Matrix8By16Fifo = MatrixFifo<uint32_t, kMatrixRows, kMatrixCols>;
// Test fixture that instantiates the factory class for DataBuffers.
class FifoTest : public testing::Test {
protected:
FifoTest() {
db_factory_ = std::make_unique<DataBufferFactory>();
controller_ = std::make_unique<ProgramErrorController>(kControllerName);
}
std::unique_ptr<DataBufferFactory> db_factory_;
std::unique_ptr<ProgramErrorController> controller_;
};
// Create scalar valued and verify attributes.
TEST_F(FifoTest, ScalarCreate) {
auto scalar_fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
EXPECT_THAT(scalar_fifo->name(), StrEq("S0"));
EXPECT_EQ(scalar_fifo->shape().size(), 1);
EXPECT_EQ(scalar_fifo->shape()[0], 1);
EXPECT_EQ(scalar_fifo->size(), sizeof(uint32_t));
EXPECT_EQ(scalar_fifo->Available(), 0);
EXPECT_EQ(scalar_fifo->Capacity(), kFifoDepth);
EXPECT_EQ(scalar_fifo->Front(), nullptr);
EXPECT_EQ(scalar_fifo->IsFull(), false);
EXPECT_EQ(scalar_fifo->IsEmpty(), true);
}
// Create vector fifo and verify attributes.
TEST_F(FifoTest, VectorCreate) {
auto vector_fifo = std::make_unique<Vector8Fifo>(nullptr, "V0", kFifoDepth);
EXPECT_THAT(vector_fifo->name(), StrEq("V0"));
EXPECT_EQ(vector_fifo->shape().size(), 1);
EXPECT_EQ(vector_fifo->shape()[0], kVectorLength);
EXPECT_EQ(vector_fifo->size(), kVectorLength * sizeof(uint32_t));
EXPECT_EQ(vector_fifo->Available(), 0);
EXPECT_EQ(vector_fifo->Capacity(), kFifoDepth);
EXPECT_EQ(vector_fifo->Front(), nullptr);
EXPECT_FALSE(vector_fifo->IsFull());
EXPECT_TRUE(vector_fifo->IsEmpty());
}
// Create matrix fifo and verify attirbutes.
TEST_F(FifoTest, MatrixCreate) {
auto matrix_fifo =
std::make_unique<Matrix8By16Fifo>(nullptr, "M0", kFifoDepth);
EXPECT_THAT(matrix_fifo->name(), StrEq("M0"));
EXPECT_EQ(matrix_fifo->shape()[0], kMatrixRows);
EXPECT_EQ(matrix_fifo->shape()[1], kMatrixCols);
EXPECT_EQ(matrix_fifo->size(), kMatrixRows * kMatrixCols * sizeof(uint32_t));
EXPECT_EQ(matrix_fifo->Available(), 0);
EXPECT_EQ(matrix_fifo->Capacity(), kFifoDepth);
EXPECT_EQ(matrix_fifo->Front(), nullptr);
EXPECT_FALSE(matrix_fifo->IsFull());
EXPECT_TRUE(matrix_fifo->IsEmpty());
}
// Verify scalar databuffer api.
TEST_F(FifoTest, ScalarDataBuffer) {
// Allocate fifo and make sure data_buffer is nullptr.
auto scalar_fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
EXPECT_EQ(scalar_fifo->Front(), nullptr);
// Allocate a data buffer of the right byte size and bind it to the fifo.
DataBuffer *db = db_factory_->Allocate(scalar_fifo->size());
scalar_fifo->SetDataBuffer(db);
EXPECT_EQ(scalar_fifo->Available(), 1);
EXPECT_FALSE(scalar_fifo->IsFull());
EXPECT_FALSE(scalar_fifo->IsEmpty());
// Verify reference count is 2, then DecRef.
EXPECT_EQ(db->ref_count(), 2);
db->DecRef();
EXPECT_EQ(scalar_fifo->Front(), db);
}
// Verify vector databuffer api.
TEST_F(FifoTest, VectorDataBuffer) {
// Allocate fifo and make sure data_buffer is nullptr.
auto vector_fifo = std::make_unique<Vector8Fifo>(nullptr, "V0", kFifoDepth);
EXPECT_EQ(vector_fifo->Front(), nullptr);
// Allocate a data buffer of the right byte size and bind it to the fifo.
DataBuffer *db = db_factory_->Allocate(vector_fifo->size());
vector_fifo->SetDataBuffer(db);
EXPECT_EQ(vector_fifo->Available(), 1);
EXPECT_FALSE(vector_fifo->IsFull());
EXPECT_FALSE(vector_fifo->IsEmpty());
// Verify reference count is 2, then DecRef.
EXPECT_EQ(db->ref_count(), 2);
db->DecRef();
EXPECT_EQ(vector_fifo->Front(), db);
}
// Verify matrix databuffer api.
TEST_F(FifoTest, MatrixDataBuffer) {
// Allocate fifo and make sure data_buffer is nullptr.
auto matrix_fifo =
std::make_unique<Matrix8By16Fifo>(nullptr, "M0", kFifoDepth);
EXPECT_EQ(matrix_fifo->Front(), nullptr);
// Allocate a data buffer of the right byte size and bind it to the fifo.
DataBuffer *db = db_factory_->Allocate(matrix_fifo->size());
matrix_fifo->SetDataBuffer(db);
EXPECT_EQ(matrix_fifo->Available(), 1);
EXPECT_FALSE(matrix_fifo->IsFull());
EXPECT_FALSE(matrix_fifo->IsEmpty());
// Verify reference count is 2, then DecRef.
EXPECT_EQ(db->ref_count(), 2);
db->DecRef();
EXPECT_EQ(matrix_fifo->Front(), db);
}
// Verify Fifo empty/full.
TEST_F(FifoTest, EmptyFullEmpty) {
auto fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
DataBuffer *db[kFifoDepth + 1];
for (int db_num = 0; db_num < kFifoDepth + 1; db_num++) {
db[db_num] = db_factory_->Allocate(fifo->size());
}
// Verify IsFull, IsEmpty, and Available as 4 DataBuffer objects are pushed.
for (int db_num = 0; db_num < kFifoDepth + 1; db_num++) {
EXPECT_EQ(fifo->IsFull(), db_num >= kFifoDepth);
EXPECT_EQ(fifo->IsEmpty(), db_num == 0);
EXPECT_EQ(fifo->Available(), db_num);
// The 4th Push will fail.
bool success = fifo->Push(db[db_num]);
EXPECT_EQ(success, db_num < kFifoDepth);
EXPECT_EQ(fifo->IsFull(), db_num >= kFifoDepth - 1);
EXPECT_EQ(fifo->IsEmpty(), false);
EXPECT_EQ(fifo->Available(), (db_num >= kFifoDepth ? db_num : db_num + 1));
}
// Verify IsFull, IsEmpty and Available as DataBuffer objects are popped.
for (int db_num = 0; db_num < kFifoDepth + 1; db_num++) {
EXPECT_EQ(fifo->Front(), db_num < kFifoDepth ? db[db_num] : nullptr);
EXPECT_EQ(fifo->Available(),
db_num > kFifoDepth - 1 ? 0 : kFifoDepth - db_num);
EXPECT_EQ(fifo->IsFull(), db_num == 0);
EXPECT_EQ(fifo->IsEmpty(), db_num > kFifoDepth - 1);
fifo->Pop();
EXPECT_EQ(fifo->Available(),
db_num > kFifoDepth - 2 ? 0 : kFifoDepth - db_num - 1);
EXPECT_EQ(fifo->IsFull(), false);
EXPECT_EQ(fifo->IsEmpty(), db_num > kFifoDepth - 2);
// Cleanup.
db[db_num]->DecRef();
}
}
TEST_F(FifoTest, Reserve) {
auto fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
EXPECT_TRUE(fifo->IsEmpty());
EXPECT_EQ(fifo->Reserved(), 0);
fifo->Reserve(kFifoDepth);
EXPECT_EQ(fifo->Reserved(), kFifoDepth);
EXPECT_FALSE(fifo->IsEmpty());
EXPECT_TRUE(fifo->IsFull());
EXPECT_FALSE(fifo->IsOverSubscribed());
DataBuffer *db[kFifoDepth];
for (int db_num = 0; db_num < kFifoDepth; db_num++) {
db[db_num] = db_factory_->Allocate(fifo->size());
fifo->Push(db[db_num]);
EXPECT_FALSE(fifo->IsEmpty());
EXPECT_TRUE(fifo->IsFull());
EXPECT_FALSE(fifo->IsOverSubscribed());
EXPECT_EQ(fifo->Reserved(), kFifoDepth - db_num - 1);
EXPECT_EQ(fifo->Available(), db_num + 1);
}
// Cleanup.
for (int db_num = 0; db_num < kFifoDepth; db_num++) {
fifo->Pop();
db[db_num]->DecRef();
}
}
TEST_F(FifoTest, Overflow) {
auto fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
fifo->Reserve(kFifoDepth + 1);
EXPECT_TRUE(fifo->IsOverSubscribed());
}
TEST_F(FifoTest, UnderflowProgramError) {
auto fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
controller_->AddProgramErrorName(kUnderflowName);
auto underflow = controller_->GetProgramError(kUnderflowName);
fifo->SetUnderflowProgramError(&underflow);
EXPECT_FALSE(controller_->HasError());
// Popping an empty fifo should cause an underflow program error.
fifo->Pop();
EXPECT_TRUE(controller_->HasError());
EXPECT_TRUE(controller_->HasUnmaskedError());
EXPECT_THAT(controller_->GetUnmaskedErrorNames()[0],
testing::StrEq(kUnderflowName));
controller_->ClearAll();
// Accessing the Front of an empty fifo should cause an underflow program
// error.
(void)fifo->Front();
EXPECT_TRUE(controller_->HasError());
EXPECT_TRUE(controller_->HasUnmaskedError());
EXPECT_THAT(controller_->GetUnmaskedErrorNames()[0],
testing::StrEq(kUnderflowName));
}
TEST_F(FifoTest, OverflowProgramError) {
auto fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
controller_->AddProgramErrorName(kOverflowName);
auto overflow = controller_->GetProgramError(kOverflowName);
fifo->SetOverflowProgramError(&overflow);
EXPECT_FALSE(controller_->HasError());
DataBuffer *db[kFifoDepth + 1];
for (int db_num = 0; db_num < kFifoDepth + 1; db_num++) {
db[db_num] = db_factory_->Allocate(fifo->size());
fifo->Push(db[db_num]);
}
EXPECT_TRUE(fifo->IsFull());
// No overflow set since there are no reserved slots. However, the overflow
// program error should be set.
EXPECT_FALSE(fifo->IsOverSubscribed());
EXPECT_TRUE(controller_->HasError());
EXPECT_TRUE(controller_->HasUnmaskedError());
EXPECT_THAT(controller_->GetUnmaskedErrorNames()[0],
testing::StrEq(kOverflowName));
controller_->ClearAll();
// Cleanup data buffers.
for (int db_num = 0; db_num < kFifoDepth + 1; db_num++) {
db[db_num]->DecRef();
}
}
TEST_F(FifoTest, Configuration) {
constexpr char kConfig[] = R"pb(
name: "S0",
configuration { name: "S0" uint64_value: 15 }
)pb";
proto::ComponentData fromText;
EXPECT_TRUE(
google::protobuf::TextFormat::ParseFromString(kConfig, &fromText));
auto fifo = std::make_unique<ScalarFifo>(nullptr, "S0", kFifoDepth);
EXPECT_TRUE(fifo->Import(fromText).ok());
}
} // namespace
} // namespace generic
} // namespace sim
} // namespace mpact