diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 40c62330..1a68b767 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -13,6 +13,8 @@ endfunction() add_executable(minimal minimal.c) install_example(minimal) if(HOSTED) + add_executable(fixed-point fixed-point.cc) + install_example(fixed-point) add_executable(hello-getchar hello-getchar.c) install_example(hello-getchar) add_executable(hello-malloc hello-malloc.c) diff --git a/examples/fixed-point.cc b/examples/fixed-point.cc new file mode 100644 index 00000000..354a5050 --- /dev/null +++ b/examples/fixed-point.cc @@ -0,0 +1,62 @@ +#undef NDEBUG +#include +#include + +// This file is an example of how to use the high level fixed point +// library in fixed_point.h. The fixedpoint::Value class provides a +// typesafe wrapper for fixed point mathematical operations and +// convenience methods for accessing the integral and decimal parts. + +int main() { + // Creates a signed fixed point number with 100 as the integral part + // The full representation of the number is 0x6400 where 0x64 is the + // integer part and 0x00 is the fractional part. + FixedPoint<8, 8> basic_number{100}; + + // Math operators work out of the box + // This will add 19 to the integral part + basic_number += 19; + + // Integer and fractional parts can be accessed individually + assert( basic_number.as_i() == 119 ); + + // Now for the fun part, here's a second number but with a fractional part + // This number in hexadecimal is 0x0110 + FixedPoint<8, 8> second_number{1, 16}; + + // Subtraction works as well. + basic_number -= second_number; + // The new basic_number integral part is now 117 (since we subtracted a fraction part) + assert( basic_number.as_i() == 117 ); + // The fractional part is + assert( basic_number.as_f() == 0xf0 ); + + // Now that the basics are out of the way, let's start using the helper methods + // in the literals namespace + using namespace fixedpoint_literals; + + // For the common 8.8 and 12.4 fixed point numbers, user defined literals are + // provided for converting from floating point numbers at compile time. + // This converts 9.87 into an 12.4 number (0x009e) + auto third_number = 9.87_12_4; + + // Comparisons work between fixed point numbers or integer numbers + // Additionally fixed point numbers will be converted to other fixed point + // numbers automatically when performing math operations. + assert(third_number + second_number <= basic_number); + assert(basic_number < 119); + + // fixed point numbers can be signed or unsigned as well with the optional + // third parameter. They are signed by default like other integer types + // There is also a matching literal type for unsigned numbers `_u8_8` + // as well as an explicit signed version `_s8_8` + fu8_8 unsigned_number = 40.4_u8_8; + // The type is not required of course, the following also works. + auto unsigned_number2 = 40.4_u8_8; + + unsigned_number += 200; + // Since its unsigned, it will be treated as a positive value instead + assert ( unsigned_number > 200 ); + + return 0; +} diff --git a/mos-platform/common/include/fixed_point.h b/mos-platform/common/include/fixed_point.h new file mode 100644 index 00000000..d5e8b20a --- /dev/null +++ b/mos-platform/common/include/fixed_point.h @@ -0,0 +1,576 @@ +// Copyright 2024 LLVM-MOS Project +// Licensed under the Apache License, Version 2.0 with LLVM Exceptions. +// See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license +// information. + +#ifndef _FIXED_POINT_H +#define _FIXED_POINT_H + +#include +#include +#include +#include + +#if __cplusplus >= 202002L +#define __fp_consteval consteval +#else +#define __fp_consteval constexpr +#endif + +/// Numeric Wrapper for Fixed Point math +/// +/// Without dedicated floating point hardware, fixed point math is a good choice +/// for many llvm-mos targets. This class wraps common fixed point idioms +/// to provide a typesafe way to perform math operations that represent +/// fractional values. +/// +/// For a quick primer on fixed point math, consider a simple case of a 8.8 +/// fixed point number. The first number represents the number of bits used to +/// hold the Integer portion of the value, and the second 8 bits hold the +/// fractional portion. This has some very useful properties for the 6502 such +/// as when adding two fixed point numbers with the same size, the math is the +/// simply 16 bit math. Another benefit is should you only want to use the +/// integer portion of the value, accessing this comes with no additional cost +/// because its merely the high bytes. This property is not true for all +/// combination of sizes, but in general, it still is reasonably quick to access +/// the higher byte by shifting as needed. +/// +/// In a little bit more detail, a fixed point number can be considered a +/// regular 16 bit number that is implicitly divided by the size of the +/// fractional portion. With our 8.8 example, the Fractional portion can +/// represent 256 values total, meaning the value in the fractional part +/// represents a number divided by 256. As a practical example, a 16 bit number +/// `0x5060` , and when divided by `0x100` yields `0x50` with a remainder of +/// `0x60`. This class makes that divider a part of the type of the class, +/// allowing seamless math operations as if its a normal integer, but with the +/// correct shifting when combining with other values. +template +class FixedPoint; + +template class FixedPoint { + static __fp_consteval auto bytesForBits(intmax_t v) { return ((v + 7) / 8); } + static constexpr auto storage_size = bytesForBits(IntSize + FracSize) * 8; + + // The result type for binary operations, similar to the C integer rules. The + // larger of the integer and fractional sizes are used. If the integer sizes + // differ, has the signedness of the larger. Otherwise, signed iff both + // are signed. + template + using BinaryResultT = + FixedPoint<(OI > IntSize ? OI : IntSize), (OF > FracSize ? OF : FracSize), + (OI > IntSize ? S + : OI < IntSize ? Signed + : S && Signed)>; + +public: + using IntType = + std::conditional_t; + using FracType = unsigned _BitInt(FracSize); + using StorageType = std::conditional_t; + +private: + // Underlying storage for the type + union { + StorageType val; + struct { + FracType f : FracSize; + IntType i : IntSize; + }; + }; + +public: + // Constructors + template , bool> = true> + [[clang::always_inline]] constexpr FixedPoint(T i) + : val((StorageType)i << FracSize) {} + + /// Constructor for setting both the integral and fractional part + [[clang::always_inline]] constexpr FixedPoint(IntType i, FracType f) + : val((StorageType)i << FracSize | f) {} + + [[clang::always_inline]] constexpr FixedPoint(const FixedPoint &o) + : val(o.val) {} + [[clang::always_inline]] constexpr FixedPoint &operator=(FixedPoint o) { + val = o.val; + return *this; + } + + /// Constructor to convert floating point values into fixed point + /// Multiplying by a power of two only increases the exponent of the + /// floating point number, which is exact so long as overflow does not occur. + /// Rounding a floating point number to whole is always exact. + template , bool> = true> + [[clang::always_inline]] __fp_consteval explicit FixedPoint(T f) { + set(f * ((StorageType)1 << FracSize)); + } + + // Implicit conversion. Like C/C++ integers, silently truncate on conversion + // to a smaller type. + template + [[clang::always_inline]] constexpr FixedPoint(FixedPoint o) { + if constexpr (FracSize > OF) + *this = FixedPoint(o.as_i(), (FracType)o.as_f() << (FracSize - OF)); + else + *this = FixedPoint(o.as_i(), o.as_f() >> (OF - FracSize)); + } + + // Direct value accessor and setter methods + + /// Returns just the integral portion + [[clang::always_inline]] constexpr IntType as_i() const { return i; } + /// Returns just the fractional portion + [[clang::always_inline]] constexpr FracType as_f() const { return f; } + /// Returns the entire value + [[clang::always_inline]] constexpr StorageType get() const { return val; } + /// Update just the integral portion + [[clang::always_inline]] constexpr void set_i(IntType value) { i = value; } + /// Update just the fractional portion + [[clang::always_inline]] constexpr void set_f(FracType value) { f = value; } + /// Update the entire value + [[clang::always_inline]] constexpr void set(StorageType value) { + val = value; + } + + /// Convert the fixed point value to a new fixed point value with a different + /// size. This is just a convenience function to explicitly convert to a new + /// fixed point type. + template + [[clang::always_inline]] constexpr FixedPoint as() { + return FixedPoint(*this); + } + + [[clang::always_inline]] constexpr bool is_signed() const { return Signed; } + [[clang::always_inline]] constexpr intmax_t bitcount() const { + return IntSize + FracSize; + } + [[clang::always_inline]] constexpr intmax_t int_bitcount() const { + return IntSize; + } + [[clang::always_inline]] constexpr intmax_t frac_bitcount() const { + return FracSize; + } + + // Operator overloads + + // Unary operators + [[clang::always_inline]] constexpr FixedPoint operator-() const { + FixedPoint n = *this; + n.set(-n.get()); + return n; + } + [[clang::always_inline]] constexpr FixedPoint operator~() const { + FixedPoint n = *this; + n.set(~n.get()); + return n; + } + + // Bitwise operators + [[clang::always_inline]] constexpr FixedPoint &operator%=(FixedPoint o) { + val %= o.val; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator%(FixedPoint o) const { + FixedPoint n = *this; + n %= o; + return n; + } + [[clang::always_inline]] constexpr FixedPoint &operator&=(FixedPoint o) { + val &= o.val; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator&(FixedPoint o) const { + FixedPoint n = *this; + n &= o; + return n; + } + [[clang::always_inline]] constexpr FixedPoint &operator|=(FixedPoint o) { + val |= o.val; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator|(FixedPoint o) const { + FixedPoint n = *this; + n |= o; + return n; + } + [[clang::always_inline]] constexpr FixedPoint &operator^=(FixedPoint o) { + val ^= o.val; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator^(FixedPoint o) const { + FixedPoint n = *this; + n ^= o; + return n; + } + + // Arithimetic operators for same sized types + [[clang::always_inline]] constexpr FixedPoint &operator+=(FixedPoint o) { + val += o.val; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator+(FixedPoint o) const { + FixedPoint n = *this; + n += o; + return n; + } + + [[clang::always_inline]] constexpr FixedPoint &operator-=(FixedPoint o) { + val -= o.val; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator-(FixedPoint o) const { + FixedPoint n = *this; + n -= o; + return n; + } + + [[clang::always_inline]] constexpr FixedPoint &operator/=(FixedPoint o) { + val /= o.get(); + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator/(FixedPoint o) const { + FixedPoint n = *this; + n /= o; + return n; + } + + [[clang::always_inline]] constexpr FixedPoint &operator*=(FixedPoint o) { + // Fixed point mult is (n * m / FracSize) + // Expand the immediate value before multiplying + FixedPoint temp{*this}; + FixedPoint other{o}; + // Truncate the final result to fit inside our value + val = (temp.get() * other.get()) >> FracSize; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint operator*(FixedPoint o) const { + FixedPoint n = *this; + n *= o; + return n; + } + + [[clang::always_inline]] constexpr FixedPoint &operator>>=(StorageType v) { + val >>= v; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint + operator>>(StorageType v) const { + FixedPoint n = *this; + n >>= v; + return n; + } + + [[clang::always_inline]] constexpr FixedPoint &operator<<=(StorageType v) { + val <<= v; + return *this; + } + [[clang::always_inline]] constexpr FixedPoint + operator<<(StorageType v) const { + FixedPoint n = *this; + n <<= v; + return n; + } + + [[clang::always_inline]] FixedPoint &operator++() { + i += 1; + return *this; + } + [[clang::always_inline]] FixedPoint operator++(int) { + FixedPoint old = *this; + ++*this; + return old; + } + + template + [[clang::always_inline]] constexpr auto + operator+(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + l += r; + return l; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator+=(FixedPoint o) { + *this = *this + o; + return *this; + } + + template + [[clang::always_inline]] constexpr auto + operator-(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + l -= r; + return l; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator-=(FixedPoint o) { + *this = *this - o; + return *this; + } + template + [[clang::always_inline]] constexpr auto + operator%(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + l %= r; + return l; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator%=(FixedPoint o) { + *this = *this % o; + return *this; + } + template + [[clang::always_inline]] constexpr auto + operator^(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + l ^= r; + return l; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator^=(FixedPoint o) { + *this = *this ^ o; + return *this; + } + template + [[clang::always_inline]] constexpr auto + operator&(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + l &= r; + return l; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator&=(FixedPoint o) { + *this = *this & o; + return *this; + } + template + [[clang::always_inline]] constexpr auto + operator|(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + l |= r; + return l; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator|=(FixedPoint o) { + *this = *this | o; + return *this; + } + + template + [[clang::always_inline]] constexpr auto + operator*(FixedPoint o) const { + BinaryResultT l = *this; + BinaryResultT r = o; + return l * r; + } + template + [[clang::always_inline]] constexpr FixedPoint & + operator*=(FixedPoint o) { + *this = *this * o; + return *this; + } + + // Comparison overloads + [[clang::always_inline]] constexpr bool + operator==(const FixedPoint &o) const noexcept { + return val == o.val; + } + [[clang::always_inline]] constexpr bool + operator<(const FixedPoint &o) const noexcept { + return val < o.val; + } + [[clang::always_inline]] constexpr bool + operator!=(const FixedPoint &o) const noexcept { + return val != o.val; + } + [[clang::always_inline]] constexpr bool + operator>(const FixedPoint &o) const noexcept { + return val > o.val; + } + [[clang::always_inline]] constexpr bool + operator>=(const FixedPoint &o) const noexcept { + return val >= o.val; + } + [[clang::always_inline]] constexpr bool + operator<=(const FixedPoint &o) const noexcept { + return val <= o.val; + } + template + [[clang::always_inline]] constexpr auto + operator==(FixedPoint o) const noexcept { + BinaryResultT l = *this; + BinaryResultT r = o; + return l == r; + } + template + [[clang::always_inline]] constexpr auto + operator<(FixedPoint o) const noexcept { + BinaryResultT l = *this; + BinaryResultT r = o; + return l < r; + } + template + [[clang::always_inline]] constexpr auto + operator!=(FixedPoint o) const noexcept { + BinaryResultT l = *this; + BinaryResultT r = o; + return l != r; + } + template + [[clang::always_inline]] constexpr auto + operator>(FixedPoint o) const noexcept { + BinaryResultT l = *this; + BinaryResultT r = o; + return l > r; + } + template + [[clang::always_inline]] constexpr auto + operator>=(FixedPoint o) const noexcept { + BinaryResultT l = *this; + BinaryResultT r = o; + return l >= r; + } + template + [[clang::always_inline]] constexpr auto + operator<=(FixedPoint o) const noexcept { + BinaryResultT l = *this; + BinaryResultT r = o; + return l <= r; + } +}; + +/// Helper namespace that contains a few common types and user defined literals +/// for converting from floating point numbers to fixed point numbers at compile +/// time. Usage example: +/// +/// using namespace fixedpoint::literals; +/// fu8_8 number = 180.44_u8_8; // creates a number at compile-time == 0xb4e1 +/// f8_8 value = -60.89_s8_8; // values can be signed or unsigned +/// auto inferred = 107.3_12_4; // default is signed just like other int types +namespace fixedpoint_literals { + +using fs8_8 = FixedPoint<8, 8, true>; +using fs12_4 = FixedPoint<12, 4, true>; +using fs16_8 = FixedPoint<16, 8, true>; +using fs8_16 = FixedPoint<8, 16, true>; +using fs12_12 = FixedPoint<12, 12, true>; +using fs16_16 = FixedPoint<16, 16, true>; +using fs24_8 = FixedPoint<24, 8, true>; + +using fu8_8 = FixedPoint<8, 8, false>; +using fu12_4 = FixedPoint<12, 4, false>; +using fu16_8 = FixedPoint<16, 8, false>; +using fu8_16 = FixedPoint<8, 16, false>; +using fu12_12 = FixedPoint<12, 12, false>; +using fu16_16 = FixedPoint<16, 16, false>; +using fu24_8 = FixedPoint<24, 8, false>; + +using f8_8 = fs8_8; +using f12_4 = fs12_4; +using f16_8 = fs16_8; +using f8_16 = fs8_16; +using f12_12 = fs12_12; +using f16_16 = fs16_16; +using f24_8 = fs24_8; + +[[clang::always_inline]] __fp_consteval fs8_8 +operator""_s8_8(long double fixed) { + return FixedPoint<8, 8, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs12_4 +operator""_s12_4(long double fixed) { + return FixedPoint<12, 4, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs16_8 +operator""_s16_8(long double fixed) { + return FixedPoint<16, 8, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs8_16 +operator""_s8_16(long double fixed) { + return FixedPoint<8, 16, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs12_12 +operator""_s12_12(long double fixed) { + return FixedPoint<12, 12, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs16_16 +operator""_s16_16(long double fixed) { + return FixedPoint<16, 16, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs24_8 +operator""_s24_8(long double fixed) { + return FixedPoint<24, 8, true>{fixed}; +} + +[[clang::always_inline]] __fp_consteval fu8_8 +operator""_u8_8(long double fixed) { + return FixedPoint<8, 8, false>{fixed}; +} +[[clang::always_inline]] __fp_consteval fu12_4 +operator""_u12_4(long double fixed) { + return FixedPoint<12, 4, false>{fixed}; +} +[[clang::always_inline]] __fp_consteval fu16_8 +operator""_u16_8(long double fixed) { + return FixedPoint<16, 8, false>{fixed}; +} +[[clang::always_inline]] __fp_consteval fu8_16 +operator""_u8_16(long double fixed) { + return FixedPoint<8, 16, false>{fixed}; +} +[[clang::always_inline]] __fp_consteval fu12_12 +operator""_u12_12(long double fixed) { + return FixedPoint<12, 12, false>{fixed}; +} +[[clang::always_inline]] __fp_consteval fu16_16 +operator""_u16_16(long double fixed) { + return FixedPoint<16, 16, false>{fixed}; +} +[[clang::always_inline]] __fp_consteval fu24_8 +operator""_u24_8(long double fixed) { + return FixedPoint<24, 8, false>{fixed}; +} + +[[clang::always_inline]] __fp_consteval fs8_8 +operator""_8_8(long double fixed) { + return FixedPoint<8, 8, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs12_4 +operator""_12_4(long double fixed) { + return FixedPoint<12, 4, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs16_8 +operator""_16_8(long double fixed) { + return FixedPoint<16, 8, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs8_16 +operator""_8_16(long double fixed) { + return FixedPoint<8, 16, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs12_12 +operator""_12_12(long double fixed) { + return FixedPoint<12, 12, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs16_16 +operator""_16_16(long double fixed) { + return FixedPoint<16, 16, true>{fixed}; +} +[[clang::always_inline]] __fp_consteval fs24_8 +operator""_24_8(long double fixed) { + return FixedPoint<24, 8, true>{fixed}; +} +} // namespace fixedpoint_literals + +#endif // _FIXED_POINT_H