1
0
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:
Thomas Harte
2026-03-06 15:30:51 -05:00
parent e986ba011c
commit 4e680b1191

View File

@@ -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_;
};