blob: 0d2216840e25a4e37da4a67cd7f17f6ce5fbdb5f [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.
#ifndef MPACT_SIM_GENERIC_FIFO_H_
#define MPACT_SIM_GENERIC_FIFO_H_
#include <any>
#include <cstdint>
#include <deque>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "mpact/sim/generic/arch_state.h"
#include "mpact/sim/generic/component.h"
#include "mpact/sim/generic/config.h"
#include "mpact/sim/generic/data_buffer.h"
#include "mpact/sim/generic/operand_interface.h"
#include "mpact/sim/generic/program_error.h"
#include "mpact/sim/generic/state_item.h"
#include "mpact/sim/generic/state_item_base.h"
// Contains the basic generic definitions for classes used to model
// fifo state in simulated architectures. Fifos can contain scalar,
// one-dimensional vector, or two-dimensional array values.
namespace mpact {
namespace sim {
namespace generic {
class FifoBase;
template <typename T, typename Enable = void>
class FifoSourceOperand : public SourceOperandInterface {
public:
// Constructor. Note, default constructor deleted.
explicit FifoSourceOperand(FifoBase *fifo);
FifoSourceOperand(FifoBase *fifo, const std::string op_name);
FifoSourceOperand() = delete;
// These accessor methods are defined to satisfy the interface. However,
// in many cases, the purpose of the fifo is only to hold the underlying
// DataBuffer instance, until popped in its entirety. It is therefore
// expected that these accessors will not be used much.
// Accessor methods call into the fifo_ data_buffer to obtain the values.
// The index is in one dimension. For scalar registers it should always be
// zero, for vector registers it is the element index. For higher order
// register shapes it should be the linearized row-major order index.
bool AsBool(int i) override;
int8_t AsInt8(int i) override;
uint8_t AsUint8(int i) override;
int16_t AsInt16(int i) override;
uint16_t AsUint16(int i) override;
int32_t AsInt32(int i) override;
uint32_t AsUint32(int i) override;
int64_t AsInt64(int i) override;
uint64_t AsUint64(int i) override;
// Returns the FifoBase object wrapped in absl::any.
std::any GetObject() const override { return std::any(fifo_); }
// Returns the shape of the fifo elements.
std::vector<int> shape() const override;
std::string AsString() const override { return op_name_; }
private:
FifoBase *fifo_;
std::string op_name_;
};
// Fifo destination operand type with element value type T. It is agnostic
// of the actual structure of the underlying fifo element (scalar, vector,
// matrix).
template <typename T>
class FifoDestinationOperand : public DestinationOperandInterface {
public:
// Constructors
FifoDestinationOperand(FifoBase *fifo, int latency, std::string op_name);
FifoDestinationOperand(FifoBase *fifo, int latency);
FifoDestinationOperand() = delete;
// Initializes the DataBuffer instance so that when Submit is called, it can
// be entered into the correct delay line, with the correct latency, targeting
// the correct fifo.
void InitializeDataBuffer(DataBuffer *db) override;
// Since a fifo stores multiple values, this method will return a nullptr
// as it does not make sense to copy the value from a fifo into a destination
// DataBuffer instance that is destined for that fifo.
DataBuffer *CopyDataBuffer() override { return nullptr; }
// Allocates and returns an initialized DataBuffer instance.
DataBuffer *AllocateDataBuffer() override;
// Returns the FifoBase object wrapped in absl::any.
std::any GetObject() const override { return std::any(fifo_); }
// Returns the latency associated with writes to this fifo operand.
int latency() const override { return latency_; }
// Returns the shape of the underlying fifo element (the number of elements in
// each dimension). For instance {0} indicates a scalar quantity, whereas
// {128} indicates an 128 element vector quantity.
std::vector<int> shape() const override;
std::string AsString() const override { return op_name_; }
private:
FifoBase *fifo_;
DataBufferFactory *db_factory_;
int latency_;
DataBufferDelayLine *delay_line_;
std::string op_name_;
};
// Base class for fifo types. Implements DataBufferInterface that
// the base class StateItemBase does not.
// The Fifo class supports the ability to reserve slots in the fifo for future
// pushes of data. The reserved slots are counted for the purpose of determining
// IsFull() or IsEmpty(), but are not counted in the number of Available()
// elements. The use of the reservation capability is not required to push data,
// but is implemented to provide modeling support for architectures where the
// allocation of fifo slots are separated from the actual push of data.
// No default constructor - should only be constructed from the derived classes.
class FifoBase : public StateItemBase, public Component {
// Only constructed from derived classes.
protected:
FifoBase(class ArchState *arch_state, absl::string_view name,
const std::vector<int> &shape, int element_size,
int default_capacity);
FifoBase() = delete;
FifoBase(const FifoBase &) = delete;
public:
~FifoBase() override;
public:
// Pushes the DataBuffer to the fifo provided there is space available.
void SetDataBuffer(DataBuffer *db) override;
// Returns true if the count of reserved and full slots equals or exceeds
// fifo capacity.
virtual bool IsFull() const;
// Returns true if the fifo is empty and has zero slots reserved.
virtual bool IsEmpty() const;
// Returns true if the sum of reserved and full slots exceeds fifo capacity.
// The fifo will not accept pushes once full, so the overflow can only only
// be true if there are reserved slots.
virtual bool IsOverSubscribed() const;
// Reserves count slots in the fifo for future pushes. There is no check on
// whether this causes overflow. This allows for a Reserve to be performed in
// "parallel" with a Pop() without causing an error. If needed, an overflow
// check should be performed at the end of the simulated cycle.
virtual void Reserve(int count);
// Pushes a value into the fifo, returns true if successful. Reference count
// of the DataBuffer is incremented as part of Push. If the reserved count
// is greater than zero it will decrement the reserved count. If the push
// would overflow the fifo it returns false, and raises the overflow program
// error if set.
virtual bool Push(DataBuffer *db);
// Returns a pointer to the DataBuffer at the front of the fifo. If the fifo
// is empty, the nullptr is returned and the underflow program error, if set,
// is raised.
DataBuffer *Front() const;
// Removes the front element of the fifo and decrements its reference count.
// If the fifo is empty, the underflow program error, if set, is raised.
virtual void Pop();
// Returns max depth of fifo.
unsigned Capacity() const { return capacity_; }
// Returns the number of elements currently held in the fifo.
unsigned Available() const;
// Returns the number of reserved slots.
unsigned Reserved() const { return reserved_; }
// Setters for ProgramErrors
void SetOverflowProgramError(std::unique_ptr<ProgramError> *program_error) {
overflow_program_error_ = std::move(*program_error);
}
void SetUnderflowProgramError(std::unique_ptr<ProgramError> *program_error) {
underflow_program_error_ = std::move(*program_error);
}
protected:
// Configuration import.
absl::Status ImportSelf(
const mpact::sim::proto::ComponentData &component_data) override;
ProgramError *overflow_program_error() {
return overflow_program_error_.get();
}
ProgramError *underflow_program_error() {
return underflow_program_error_.get();
}
private:
Config<uint64_t> depth_;
std::unique_ptr<ProgramError> overflow_program_error_;
std::unique_ptr<ProgramError> underflow_program_error_;
std::string name_;
unsigned capacity_;
unsigned reserved_;
std::deque<DataBuffer *> fifo_;
};
// Scalar valued fifo with value type ElementType.
template <typename ElementType>
using Fifo = StateItem<FifoBase, ElementType, FifoSourceOperand<ElementType>,
FifoDestinationOperand<ElementType>>;
// Fifo of N long vectors with element value type ElementType.
template <typename ElementType, int N>
using VectorFifo =
StateItem<FifoBase, ElementType, FifoSourceOperand<ElementType>,
FifoDestinationOperand<ElementType>, N>;
// Fifo of MxN sized matrices with element value type ElementType.
template <typename ElementType, int M, int N>
using MatrixFifo =
StateItem<FifoBase, ElementType, FifoSourceOperand<ElementType>,
FifoDestinationOperand<ElementType>, M, N>;
template <typename T, typename Enable>
std::vector<int> FifoSourceOperand<T, Enable>::shape() const {
return fifo_->shape();
}
// Helper templates for the partial specialiations below.
template <typename T>
using EnableIfIntegral =
typename std::enable_if<std::is_integral<T>::value, void>::type;
template <typename T>
using EnableIfNotIntegral =
typename std::enable_if<!std::is_integral<T>::value, void>::type;
// Helper function used in the partial specialization below.
template <
typename F, typename T,
typename std::enable_if<std::is_signed<T>::value, T>::type * = nullptr>
inline T HelperAs(const FifoBase *fifo, int i) {
DataBuffer *db = fifo->Front();
if (nullptr == db) {
return static_cast<T>(0);
}
return static_cast<T>(db->Get<typename std::make_signed<F>::type>(i));
}
template <
typename F, typename T,
typename std::enable_if<std::is_unsigned<T>::value, T>::type * = nullptr>
inline T HelperAs(const FifoBase *fifo, int i) {
DataBuffer *db = fifo->Front();
if (nullptr == db) {
return static_cast<T>(0);
}
return static_cast<T>(db->Get<typename std::make_unsigned<F>::type>(i));
}
template <typename T, typename Enable>
FifoSourceOperand<T, Enable>::FifoSourceOperand(FifoBase *fifo,
const std::string op_name)
: fifo_(fifo), op_name_(op_name) {}
template <typename T, typename Enable>
FifoSourceOperand<T, Enable>::FifoSourceOperand(FifoBase *fifo)
: FifoSourceOperand(fifo, fifo->name()) {}
template <typename T>
class FifoSourceOperand<T, EnableIfIntegral<T>>
: public SourceOperandInterface {
public:
// Constructors. Note, default constructor deleted.
FifoSourceOperand(FifoBase *fifo, const std::string op_name)
: fifo_(fifo), op_name_(op_name) {}
explicit FifoSourceOperand(FifoBase *fifo)
: FifoSourceOperand(fifo, fifo->name()) {}
FifoSourceOperand() = delete;
// These accessor methods are defined to satisfy the interface. However,
// in many cases, the purpose of the fifo is only to hold the underlying
// DataBuffer instance, until popped in its entirety. It is therefore
// expected that these accessors will not be used much.
// Accessor methods call into the fifo_ data_buffer to obtain the values.
// The index is in one dimension. For scalar registers it should always be
// zero, for vector registers it is the element index. For higher order
// register shapes it should be the linearized row-major order index.
bool AsBool(int i) override { return HelperAs<T, bool>(fifo_, i); }
int8_t AsInt8(int i) override { return HelperAs<T, int8_t>(fifo_, i); }
uint8_t AsUint8(int i) override { return HelperAs<T, uint8_t>(fifo_, i); }
int16_t AsInt16(int i) override { return HelperAs<T, int16_t>(fifo_, i); }
uint16_t AsUint16(int i) override { return HelperAs<T, uint16_t>(fifo_, i); }
int32_t AsInt32(int i) override { return HelperAs<T, int32_t>(fifo_, i); }
uint32_t AsUint32(int i) override { return HelperAs<T, uint32_t>(fifo_, i); }
int64_t AsInt64(int i) override { return HelperAs<T, int64_t>(fifo_, i); }
uint64_t AsUint64(int i) override { return HelperAs<T, uint64_t>(fifo_, i); }
// Returns the FifoBase object wrapped in absl::any.
std::any GetObject() const override { return std::any(fifo_); }
// Returns the shape of the fifo elements.
std::vector<int> shape() const override { return fifo_->shape(); }
std::string AsString() const override { return op_name_; }
private:
FifoBase *fifo_;
std::string op_name_;
};
// This is a parial specialization of the Source operand class. This is used
// when the element type stored in the data buffer is not an integral type. This
// is primarily for when the fifo element type really doesn't model a register
// value per se, but a more complex structure such as a dma descriptor. In this
// case, the value interface is not intended to be used, but instead the fifo
// is accessed by using the GetObject and casting it to the appropriate type in
// order to access the source element.
template <typename T>
class FifoSourceOperand<T, EnableIfNotIntegral<T>>
: public SourceOperandInterface {
public:
// Constructors. Note, default constructor deleted.
FifoSourceOperand(FifoBase *fifo, const std::string op_name)
: fifo_(fifo), op_name_(op_name) {}
explicit FifoSourceOperand(FifoBase *fifo)
: FifoSourceOperand(fifo, fifo->name()) {}
FifoSourceOperand() = delete;
// These accessor methods are defined to satisfy the interface.
bool AsBool(int) override { return false; }
int8_t AsInt8(int i) override { return 0; }
uint8_t AsUint8(int i) override { return 0; }
int16_t AsInt16(int i) override { return 0; }
uint16_t AsUint16(int i) override { return 0; }
int32_t AsInt32(int i) override { return 0; }
uint32_t AsUint32(int i) override { return 0; }
int64_t AsInt64(int i) override { return 0; }
uint64_t AsUint64(int i) override { return 0; }
std::string AsString() const override { return fifo_->name(); }
// Returns the FifoBase object wrapped in absl::any.
std::any GetObject() const override { return std::any(fifo_); }
// Returns the shape of the fifo elements.
std::vector<int> shape() const override { return fifo_->shape(); }
private:
FifoBase *fifo_;
std::string op_name_;
};
template <typename T>
FifoDestinationOperand<T>::FifoDestinationOperand(FifoBase *fifo, int latency,
std::string op_name)
: fifo_(fifo),
db_factory_(fifo->arch_state()->db_factory()),
latency_(latency),
delay_line_(fifo->arch_state()->data_buffer_delay_line()),
op_name_(op_name) {}
template <typename T>
FifoDestinationOperand<T>::FifoDestinationOperand(FifoBase *fifo, int latency)
: FifoDestinationOperand(fifo, latency, fifo->name()) {}
template <typename T>
void FifoDestinationOperand<T>::InitializeDataBuffer(DataBuffer *db) {
db->set_destination(fifo_);
db->set_latency(latency_);
db->set_delay_line(delay_line_);
}
template <typename T>
DataBuffer *FifoDestinationOperand<T>::AllocateDataBuffer() {
DataBuffer *db = db_factory_->Allocate(fifo_->size());
InitializeDataBuffer(db);
return db;
}
template <typename T>
std::vector<int> FifoDestinationOperand<T>::shape() const {
return fifo_->shape();
}
} // namespace generic
} // namespace sim
} // namespace mpact
#endif // MPACT_SIM_GENERIC_FIFO_H_