blob: f8cfa9dc1092635fe785c94554d84a885ecd3e3b [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_DELAY_LINE_H_
#define MPACT_SIM_GENERIC_DELAY_LINE_H_
#include <vector>
/* Commented out for now. Not currently using threads.
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
*/
#include "absl/numeric/bits.h"
#include "mpact/sim/generic/delay_line_interface.h"
namespace mpact {
namespace sim {
namespace generic {
// The DelayLine is the generic class for implementing delay lines for
// values and deferred function calls. It allows one or more "actions" to
// be scheduled to be performed a number of cycles (calls to Advance()) in
// the future. The exact action to be performed is determined by the template
// parameter type DelayRecord. Abstractly, the DelayLine class is a circular
// buffer of lists of DelayRecords. It is implemented as a vector
// of vectors of DelayRecord instances. A given index into the first dimension
// contains a vector of the set of DelayRecord instances that specifies the
// "actions" that are performed for a given cycle in the future (based on its
// distance from the current_ index (mod vector size).
//
// The DelayLine circular buffer is always a power of two to make the
// mod operator cheap.
//
// The DelayLine class can be instantiated by passing in the DelayRecord
// type as a template argument. The DelayRecord must have a void Apply()
// method that performs the actions intended once the delay line has advanced
// to that record. Additionally, the DelayRecord type must have a constructor
// that can be called by std::vector::emplace_back().
//
// For instance, if the DelayRecord consists of a pointer and a value (with the
// intent that after a delay, the value gets written to the object pointed to
// by that pointer) a reasonable DelayRecord would be.
//
// struct MyDelayRecord {
// int *destination;
// int value;
// void Apply() {
// *destination = value;
// }
// MyDelayRecord(int *dest, int val) : destination(dest), value(val) {}
// };
//
// This class is thread safe.
template <typename DelayRecord>
class DelayLine : public DelayLineInterface {
public:
constexpr static int kDefaultDelayLineDepth = 16;
// Constructor that takes an initial minimum size
explicit DelayLine(uint32_t min_size)
: delay_line_(absl::bit_ceil(min_size)), current_(0), num_entries_(0) {
mask_ = delay_line_.size() - 1;
}
// Default constructor and destructors
DelayLine() : DelayLine(kDefaultDelayLineDepth) {}
~DelayLine() override = default;
// Add an item to the delay line with the given latency. The Ts... argument
// pack is the set of arguments to the constructor of the DelayRecord which
// is the value record type for the DelayLine. Returns the total number of
// valid elements.
template <typename... Ts>
int Add(int latency, Ts... args) /*ABSL_LOCKS_EXCLUDED(delay_line_lock_)*/ {
// absl::MutexLock dl(&delay_line_lock_);
// If the latency is longer than the delay line, resize the delay line.
if (latency >= static_cast<int>(delay_line_.size())) {
Resize(latency);
}
int pos = (latency + current_) & mask_;
delay_line_[pos].emplace_back(args...);
return ++num_entries_;
}
// Advances the delay line by a cycle and performs any actions required.
// Depending on the record type R, the actual action may vary. The initial
// actions supported generically are writing back a data buffer to a state
// instance and calling a function. Additional actions can be created by
// deriving new delay lines as needed. Returns the number of valid entries
// left in the delay line.
int Advance() override /*ABSL_LOCKS_EXCLUDED(delay_line_lock_)*/ {
// absl::MutexLock dl(&delay_line_lock_);
current_ = (current_ + 1) & mask_;
for (auto &rec : delay_line_[current_]) {
rec.Apply();
num_entries_--;
}
delay_line_[current_].clear();
return num_entries_;
}
// Returns true if the delay line is empty.
bool IsEmpty() const override { return num_entries_ == 0; }
private:
// If the latency is >= the delay line length, the delay line has to be
// resized so the latency is covered.
void Resize(
uint32_t min_size) /*ABSL_EXCLUSIVE_LOCKS_REQUIRED(delay_line_lock_)*/ {
// Compute a new size that's >= min_size and is a power of 2
// Given that the initial size is a power of two, multiplying the
// current size by two until it's greater or equal to min_size is
// sufficient.
uint32_t prev_size = delay_line_.size();
if (min_size < prev_size) {
return;
}
uint32_t new_size = absl::bit_ceil(min_size);
mask_ = new_size - 1;
delay_line_.resize(new_size);
// now need to move elements from 0 to current-1 to new locations.
for (int index = 0; index < current_; ++index) {
if (!delay_line_[index].empty()) {
delay_line_[prev_size + index] = delay_line_[index];
delay_line_[index].clear();
}
}
}
std::vector<std::vector<DelayRecord>> delay_line_;
/*ABSL_GUARDED_BY(delay_line_lock_)*/
int current_ /*ABSL_GUARDED_BY(delay_line_lock_)*/;
int mask_ /*ABSL_GUARDED_BY(delay_line_lock_)*/;
/*absl::Mutex delay_line_lock_;*/
uint32_t num_entries_;
};
} // namespace generic
} // namespace sim
} // namespace mpact
#endif // MPACT_SIM_GENERIC_DELAY_LINE_H_