mirror of
https://github.com/TomHarte/CLK.git
synced 2026-03-11 19:41:57 +00:00
Reformulate in terms of a common base value.
This commit is contained in:
@@ -12,144 +12,94 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
/*
|
||||
Informal pattern for all classes that run from a clock cycle:
|
||||
|
||||
Each will implement either or both of run_for(Cycles) and run_for(HalfCycles), as
|
||||
is appropriate.
|
||||
|
||||
Callers that are accumulating HalfCycles but want to talk to receivers that implement
|
||||
only run_for(Cycles) can use HalfCycle.flush_cycles if they have appropriate storage, or
|
||||
can wrap the receiver in HalfClockReceiver in order automatically to bind half-cycle
|
||||
storage to it.
|
||||
|
||||
Alignment rule:
|
||||
|
||||
run_for(Cycles) may be called only after an even number of half cycles. E.g. the following
|
||||
sequence will have undefined results:
|
||||
|
||||
run_for(HalfCycles(1))
|
||||
run_for(Cycles(1))
|
||||
|
||||
An easy way to ensure this as a caller is to pick only one of run_for(Cycles) and
|
||||
run_for(HalfCycles) to use.
|
||||
|
||||
Reasoning:
|
||||
|
||||
Users of this template may with to implement run_for(Cycles) and run_for(HalfCycles)
|
||||
where there is a need to implement at half-cycle precision but a faster execution
|
||||
path can be offered for full-cycle precision. Those users are permitted to assume
|
||||
phase in run_for(Cycles) and should do so to be compatible with callers that use
|
||||
only run_for(Cycles).
|
||||
|
||||
Corollary:
|
||||
|
||||
Starting from nothing, the first run_for(HalfCycles(1)) will do the **first** half
|
||||
of a full cycle. The second will do the second half. Etc.
|
||||
|
||||
*/
|
||||
constexpr bool is_power_of_two(const int v) {
|
||||
return !(v & (v - 1));
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides a class that wraps a plain int, providing most of the basic arithmetic and
|
||||
Boolean operators, but forcing callers and receivers to be explicit as to usage.
|
||||
*/
|
||||
template <class T> class WrappedInt {
|
||||
template <int ClockScale>
|
||||
requires (is_power_of_two(ClockScale))
|
||||
class Clocks {
|
||||
public:
|
||||
static constexpr int Scale = ClockScale;
|
||||
static constexpr int Mask = ~(ClockScale - 1);
|
||||
using IntType = int64_t;
|
||||
|
||||
forceinline constexpr WrappedInt(IntType l) noexcept : length_(l) {}
|
||||
forceinline constexpr WrappedInt() noexcept : length_(0) {}
|
||||
constexpr Clocks(const IntType rhs) noexcept : length_(rhs * ClockScale) {}
|
||||
constexpr Clocks() noexcept : length_(0) {}
|
||||
|
||||
forceinline T &operator =(const T &rhs) {
|
||||
length_ = rhs.length_;
|
||||
// Assignments are implemented anywhere they can't lose data.
|
||||
template <typename SourceClocks>
|
||||
requires (SourceClocks::Mask <= Mask)
|
||||
constexpr Clocks(const SourceClocks rhs) noexcept : length_(rhs.raw() & Mask) {}
|
||||
|
||||
Clocks operator =(const Clocks rhs) {
|
||||
length_ = rhs.length_ & Mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
forceinline T &operator +=(const T &rhs) {
|
||||
length_ += rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
Clocks operator +=(const Clocks rhs) { length_ += rhs.length_; return *this; }
|
||||
Clocks operator -=(const Clocks rhs) { length_ -= rhs.length_; return *this; }
|
||||
Clocks operator ++() { length_ += ClockScale; return *this; }
|
||||
Clocks operator --() { length_ -= ClockScale; return *this; }
|
||||
|
||||
Clocks operator ++(int) {
|
||||
const Clocks result = *this;
|
||||
length_ += ClockScale;
|
||||
return result;
|
||||
}
|
||||
Clocks operator --(int) {
|
||||
const Clocks result = *this;
|
||||
length_ -= ClockScale;
|
||||
return result;
|
||||
}
|
||||
|
||||
forceinline T &operator -=(const T &rhs) {
|
||||
length_ -= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
Clocks operator *=(const Clocks rhs) { *this = Clocks(length_ * rhs.length_); return *this; }
|
||||
Clocks operator /=(const Clocks rhs) { *this = Clocks(length_ / rhs.length_); return *this; }
|
||||
Clocks operator %=(const Clocks rhs) { *this = Clocks(length_ % rhs.length_); return *this; }
|
||||
Clocks operator &=(const Clocks rhs) { *this = Clocks(length_ & rhs.length_); return *this; }
|
||||
|
||||
forceinline T &operator ++() {
|
||||
++ length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
constexpr Clocks operator +(const Clocks rhs) const { return Clocks(length_ + rhs.length_); }
|
||||
constexpr Clocks operator -(const Clocks rhs) const { return Clocks(length_ - rhs.length_); }
|
||||
|
||||
forceinline T &operator ++(int) {
|
||||
length_ ++;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
constexpr Clocks operator *(const Clocks rhs) const { return Clocks(length_ * rhs.length_); }
|
||||
constexpr Clocks operator /(const Clocks rhs) const { return Clocks(length_ / rhs.length_); }
|
||||
|
||||
forceinline T &operator --() {
|
||||
-- length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
constexpr Clocks operator %(const Clocks rhs) const { return Clocks(length_ % rhs.length_); }
|
||||
constexpr Clocks operator &(const Clocks rhs) const { return Clocks(length_ & rhs.length_); }
|
||||
|
||||
forceinline T &operator --(int) {
|
||||
length_ --;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
constexpr Clocks operator -() const { return Clocks(-length_); }
|
||||
|
||||
forceinline T &operator *=(const T &rhs) {
|
||||
length_ *= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
auto operator <=>(const Clocks &) const = default;
|
||||
|
||||
forceinline T &operator /=(const T &rhs) {
|
||||
length_ /= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator %=(const T &rhs) {
|
||||
length_ %= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline T &operator &=(const T &rhs) {
|
||||
length_ &= rhs.length_;
|
||||
return *static_cast<T *>(this);
|
||||
}
|
||||
|
||||
forceinline constexpr T operator +(const T &rhs) const { return T(length_ + rhs.length_); }
|
||||
forceinline constexpr T operator -(const T &rhs) const { return T(length_ - rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator *(const T &rhs) const { return T(length_ * rhs.length_); }
|
||||
forceinline constexpr T operator /(const T &rhs) const { return T(length_ / rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator %(const T &rhs) const { return T(length_ % rhs.length_); }
|
||||
forceinline constexpr T operator &(const T &rhs) const { return T(length_ & rhs.length_); }
|
||||
|
||||
forceinline constexpr T operator -() const { return T(- length_); }
|
||||
|
||||
auto operator <=>(const WrappedInt &) const = default;
|
||||
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int,
|
||||
// which is prone silently to permit misuse.
|
||||
|
||||
/// @returns The underlying int, converted to a numeric type of your choosing, clamped to that type's range.
|
||||
template<typename Type = IntType>
|
||||
template <typename Type = IntType>
|
||||
requires std::integral<Type> || std::floating_point<Type>
|
||||
constexpr Type as() const {
|
||||
const auto value = get();
|
||||
|
||||
if constexpr (sizeof(Type) == sizeof(IntType) && std::is_integral_v<Type>) {
|
||||
if constexpr (std::is_same_v<Type, IntType>) {
|
||||
return length_;
|
||||
return value;
|
||||
} else if constexpr (std::is_signed_v<Type>) {
|
||||
// Both integers are the same size, but a signed result is being asked for
|
||||
// from an unsigned original.
|
||||
return length_ > Type(std::numeric_limits<Type>::max()) ?
|
||||
Type(std::numeric_limits<Type>::max()) : Type(length_);
|
||||
return value > Type(std::numeric_limits<Type>::max()) ?
|
||||
Type(std::numeric_limits<Type>::max()) : Type(value);
|
||||
} else {
|
||||
// An unsigned result is being asked for from a signed original.
|
||||
return length_ < 0 ? 0 : Type(length_);
|
||||
return value < 0 ? 0 : Type(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,37 +107,49 @@ public:
|
||||
}
|
||||
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
*/
|
||||
template <typename Result = T> forceinline Result divide(const T &divisor) {
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r, divisor);
|
||||
return r;
|
||||
constexpr IntType get() const {
|
||||
return length_ / ClockScale;
|
||||
}
|
||||
|
||||
/*!
|
||||
Flushes the value in @c this. The current value is returned, and the internal value
|
||||
is reset to zero.
|
||||
*/
|
||||
template <typename Result> Result flush() {
|
||||
// Jiggery pokery here; switching to function overloading avoids
|
||||
// the namespace-level requirement for template specialisation.
|
||||
Result r;
|
||||
static_cast<T *>(this)->fill(r);
|
||||
return r;
|
||||
constexpr IntType raw() const {
|
||||
return length_;
|
||||
}
|
||||
|
||||
// operator int() is deliberately not provided, to avoid accidental subtitution of
|
||||
// classes that use this template.
|
||||
|
||||
protected:
|
||||
IntType length_;
|
||||
/*!
|
||||
Caculates `*this / divisor`, converting that to `DestinationClocks`.
|
||||
Sets `*this = *this % divisor`.
|
||||
*/
|
||||
// template <typename DestinationClocks = Clocks>
|
||||
// DestinationClocks divide(const DestinationClocks divisor) {
|
||||
// //
|
||||
//
|
||||
//
|
||||
// Clocks result;
|
||||
// result.length_ = length_ / divisor.length_;
|
||||
// length_ %= divisor.length_;
|
||||
// return result;
|
||||
// }
|
||||
|
||||
/*!
|
||||
Extracts a whole number of `DestinationClock`s from `*this`.
|
||||
Leaves the residue here.
|
||||
*/
|
||||
template <typename DestinationClocks = Clocks>
|
||||
requires (DestinationClocks::Mask <= Mask)
|
||||
DestinationClocks flush() {
|
||||
const auto result = DestinationClocks(length_);
|
||||
length_ &= Mask ^ DestinationClocks::Mask;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Clocks max() { return Clocks(std::numeric_limits<IntType>::max()); }
|
||||
static Clocks min() { return Clocks(std::numeric_limits<IntType>::min()); }
|
||||
|
||||
private:
|
||||
IntType length_;
|
||||
|
||||
template <typename Type>
|
||||
static consteval bool can_represent(const Type x) {
|
||||
return std::numeric_limits<IntType>::min() <= x && std::numeric_limits<IntType>::max() >= x;
|
||||
@@ -204,104 +166,24 @@ private:
|
||||
IntType(std::numeric_limits<Type>::max()) : std::numeric_limits<IntType>::max();
|
||||
};
|
||||
|
||||
/// Describes an integer number of whole cycles: pairs of clock signal transitions.
|
||||
class Cycles: public WrappedInt<Cycles> {
|
||||
public:
|
||||
forceinline constexpr Cycles(IntType l) noexcept : WrappedInt<Cycles>(l) {}
|
||||
forceinline constexpr Cycles() noexcept : WrappedInt<Cycles>() {}
|
||||
forceinline static constexpr Cycles max() {
|
||||
return Cycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
void fill(Cycles &result, const Cycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes an integer number of half cycles: single clock signal transitions.
|
||||
class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
public:
|
||||
forceinline constexpr HalfCycles(IntType l) noexcept : WrappedInt<HalfCycles>(l) {}
|
||||
forceinline constexpr HalfCycles() noexcept : WrappedInt<HalfCycles>() {}
|
||||
forceinline static constexpr HalfCycles max() {
|
||||
return HalfCycles(std::numeric_limits<IntType>::max());
|
||||
}
|
||||
|
||||
forceinline constexpr HalfCycles(const Cycles &cycles) noexcept :
|
||||
WrappedInt<HalfCycles>(cycles.as_integral() * 2) {}
|
||||
|
||||
/// @returns The number of whole cycles completely covered by this span of half cycles.
|
||||
forceinline constexpr Cycles cycles() const {
|
||||
return Cycles(length_ >> 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
const Cycles result(length_ / half_divisor.length_);
|
||||
length_ %= half_divisor.length_;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
result = Cycles(length_ >> 1);
|
||||
length_ &= 1;
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result) {
|
||||
result.length_ = length_;
|
||||
length_ = 0;
|
||||
}
|
||||
|
||||
void fill(Cycles &result, const HalfCycles &divisor) {
|
||||
result = Cycles(length_ / (divisor.length_ << 1));
|
||||
length_ %= (divisor.length_ << 1);
|
||||
}
|
||||
|
||||
void fill(HalfCycles &result, const HalfCycles &divisor) {
|
||||
result.length_ = length_ / divisor.length_;
|
||||
length_ %= divisor.length_;
|
||||
}
|
||||
};
|
||||
|
||||
// Create a specialisation of WrappedInt::flush for converting HalfCycles to Cycles
|
||||
// without losing the fractional part.
|
||||
/// Reasons Clocks into being a count of querter cycles, building half- and whole-cycles from there.
|
||||
using Cycles = Clocks<4>;
|
||||
using HalfCycles = Clocks<2>;
|
||||
using QuarterCycles = Clocks<1>;
|
||||
|
||||
/*!
|
||||
If a component implements only run_for(Cycles), an owner can wrap it in HalfClockReceiver
|
||||
automatically to gain run_for(HalfCycles).
|
||||
Provides automated boilerplate for connecting an owner that works in one clock base to a receiver that works in another.
|
||||
*/
|
||||
template <class T> class HalfClockReceiver: public T {
|
||||
template <typename TargetT, typename SourceClocks, typename DestinationClocks>
|
||||
class ConvertedClockReceiver: public TargetT {
|
||||
public:
|
||||
using T::T;
|
||||
using TargetT::TargetT;
|
||||
|
||||
forceinline void run_for(const HalfCycles half_cycles) {
|
||||
half_cycles_ += half_cycles;
|
||||
T::run_for(half_cycles_.flush<Cycles>());
|
||||
void run_for(const SourceClocks duration) {
|
||||
source_ += duration;
|
||||
TargetT::run_for(source_.template flush<DestinationClocks>());
|
||||
}
|
||||
|
||||
private:
|
||||
HalfCycles half_cycles_;
|
||||
SourceClocks source_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user