| // 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 |