Created
August 4, 2025 16:25
-
-
Save m516/20c2778d2b9f6ebe2a54f05b75e8c3a6 to your computer and use it in GitHub Desktop.
Inefficient but flexible digital signal grouping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <array> | |
| #include <stdexcept> | |
| #include <vector> | |
| #include <memory> | |
| #include <sstream> | |
| namespace signal{ | |
| struct BitInterval { | |
| size_t start; | |
| size_t end; // Inclusive now | |
| size_t size() const { | |
| return (start > end) ? (start - end + 1) : (end - start + 1); | |
| } | |
| bool is_descending() const { | |
| return start > end; | |
| } | |
| }; | |
| using BitSequence = std::vector<BitInterval>; | |
| std::ostream& operator<<(std::ostream& os, const BitSequence& s) { | |
| os << "["; | |
| for (size_t i = 0; i < s.size(); ++i) { | |
| os << "(" << s[i].start << "," << s[i].end << ")"; | |
| if (i + 1 < s.size()) os << ", "; | |
| } | |
| os << "]"; | |
| return os; | |
| } | |
| /** | |
| * A datatype that simplifies bit manipulation for logical signals. | |
| * Example: | |
| ```cpp | |
| #include "signal.hpp" | |
| #include <iostream> | |
| int main(){ | |
| using namespace std; | |
| using namespace signal; | |
| // initializing signals to default values | |
| Signal<4> a(0b1110); | |
| Signal<8> b(0b00010110); | |
| // concatenating signals | |
| Signal<12> c({a, b}); | |
| cout << c << endl; // b111000010110 | |
| // returns bit indicies in order 0 1 4 3 2 | |
| cout << c.slice<5>({{0,1}, {4,2}}) << endl; // b01101 | |
| // reverses a signal | |
| cout << b.slice<8>({{0,7}}) << " is the same as " << b.reverse() << endl; // b01101000 is the same as b01101000 | |
| // identity | |
| cout << b << " is the same as " << b.slice<8>({{7,0}}) << endl; // b00010110 is the same as b00010110 | |
| // conversion to integral types | |
| cout << a.to_u16() << endl; // 14 | |
| // conversion from integral types | |
| b.set_to(42); | |
| cout << b << " = " << b.to_u16() << endl; // b00101010 = 42 | |
| // writing to a derived signal also writes to the original signal | |
| cout << "C=" << c << " "; | |
| cout << "B=" << b << endl; // C=b111000101010 B=b00101010 | |
| c[0] = 1; | |
| cout << "C=" << c << " "; | |
| cout << "B=" << b << endl; // C=b111000101011 B=b00101011 | |
| // explicitly copying allows two signals to disconnect, | |
| // i.e. have different states | |
| auto d = a.copy(); | |
| cout << "A=" << a << " "; | |
| cout << "D=" << d << endl; // A=b1110 D=b1110 | |
| d[0] = 1; | |
| cout << "A=" << a << " "; | |
| cout << "D=" << d << endl; // A=b1110 D=b1111 | |
| } | |
| ``` | |
| */ | |
| template<size_t N> | |
| struct Signal : public std::array<std::shared_ptr<bool>, N> { | |
| using Base = std::array<std::shared_ptr<bool>, N>; | |
| explicit Signal(unsigned long long initial_value) { | |
| unsigned long long v = initial_value; | |
| for (size_t i = 0; i < N; i++) { | |
| bool bit = v & 1; | |
| Base::operator[](i) = std::make_shared<bool>(bit); | |
| v >>= 1; | |
| } | |
| } | |
| // Concatenate constructor | |
| template<size_t... Sizes, typename... Signals> | |
| Signal(const Signal<Sizes>&... signals) { | |
| static constexpr size_t TotalSize = (Sizes + ...); | |
| static_assert(TotalSize == N, "Total size mismatch"); | |
| // Convert to tuple for indexed access | |
| auto tuple = std::make_tuple(signals...); | |
| size_t index = 0; | |
| // Apply from last to first | |
| reverse_assign<sizeof...(Sizes)>(tuple, index); | |
| } | |
| template<size_t Count, typename Tuple> | |
| void reverse_assign(const Tuple& tup, size_t& index) { | |
| reverse_assign_impl(tup, index, std::make_index_sequence<Count>{}); | |
| } | |
| template<typename Tuple, size_t... Is> | |
| void reverse_assign_impl(const Tuple& tup, size_t& index, std::index_sequence<Is...>) { | |
| // Expand in reverse order: last to first | |
| (..., assign_from_tuple<sizeof...(Is) - 1 - Is>(tup, index)); | |
| } | |
| template<size_t I, typename Tuple> | |
| void assign_from_tuple(const Tuple& tup, size_t& index) { | |
| const auto& s = std::get<I>(tup); | |
| for (size_t i = 0; i < s.size(); ++i) { | |
| this->set_ptr(index++, s.get_ptr(i)); | |
| } | |
| } | |
| // Get a writable bit | |
| bool& operator[](size_t n) { | |
| if (n >= N) { | |
| std::ostringstream msg; | |
| msg << "Attempted to access bit " << n << " in a Signal of size " << N; | |
| throw std::out_of_range(msg.str()); | |
| } | |
| return *Base::operator[](n); | |
| } | |
| // Get a non-writeable copy of bit n | |
| bool get_value(size_t n) const { | |
| if (n >= N) { | |
| std::ostringstream msg; | |
| msg << "Attempted to access bit " << n << " in a Signal of size " << N; | |
| throw std::out_of_range(msg.str()); | |
| } | |
| return *Base::operator[](n); | |
| } | |
| // Extract a sub-signal from a bit sequence | |
| template<size_t M> | |
| Signal<M> slice(const BitSequence &seq) { | |
| size_t total_requested = 0; | |
| for (const auto& interval : seq) | |
| total_requested += interval.size(); | |
| if (total_requested != M) { | |
| std::ostringstream msg; | |
| msg << "BitSequence size mismatch: expected " << M << " bits, but got " | |
| << total_requested << " from sequence: " << seq; | |
| throw std::invalid_argument(msg.str()); | |
| } | |
| std::array<std::shared_ptr<bool>, M> temp{}; | |
| size_t idx = M; | |
| for (const auto& interval : seq) { | |
| if (interval.start >= N || interval.end >= N) { | |
| std::ostringstream msg; | |
| msg << "Invalid interval: (" << interval.start << "," << interval.end | |
| << ") is out of range for signal of size " << N; | |
| throw std::out_of_range(msg.str()); | |
| } | |
| if (interval.is_descending()) { | |
| for (size_t i = interval.start;; --i) { | |
| temp[--idx] = Base::operator[](i); | |
| if (i == interval.end) break; | |
| } | |
| } else { | |
| for (size_t i = interval.start; i <= interval.end; ++i) { | |
| temp[--idx] = Base::operator[](i); | |
| } | |
| } | |
| } | |
| Signal<M> result(0); | |
| for (size_t i = 0; i < M; ++i) | |
| result.set_ptr(i, temp[i]); | |
| return result; | |
| } | |
| // Reverse the signal: bitwise mirror (shared_ptrs are mirrored too) | |
| Signal<N> reverse() const { | |
| Signal<N> result(0); | |
| for (size_t i = 0; i < N; ++i) | |
| result.set_ptr(N - 1 - i, Base::operator[](i)); | |
| return result; | |
| } | |
| // Assign shared_ptr manually | |
| void set_ptr(size_t i, std::shared_ptr<bool> ptr) { | |
| Base::operator[](i) = ptr; | |
| } | |
| // Get shared_ptr manually | |
| std::shared_ptr<bool> get_ptr(size_t n) const { | |
| if (n >= N) { | |
| std::ostringstream msg; | |
| msg << "Attempted to access bit " << n << " in a Signal of size " << N; | |
| throw std::out_of_range(msg.str()); | |
| } | |
| return Base::operator[](n); | |
| } | |
| // Deep copy | |
| Signal copy() { | |
| Signal result(0); | |
| for (size_t i = 0; i < N; ++i) | |
| result.set_ptr(i, std::make_shared<bool>(*Base::operator[](i))); | |
| return result; | |
| } | |
| // converts the value whole signal at this time to an integer | |
| template<typename T> | |
| T to() const { | |
| static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>, "to<T>() requires unsigned integral type"); | |
| static_assert(N <= sizeof(T) * 8, "Signal is too wide to convert to target type"); | |
| T result = 0; | |
| for (size_t i = 0; i < N; ++i) { | |
| if (get_value(i)) { | |
| result |= (T(1) << i); | |
| } | |
| } | |
| return result; | |
| } | |
| // converts the value whole signal at this time to an uint_8 | |
| uint8_t to_u8() const { return to<uint8_t>(); } | |
| // converts the value whole signal at this time to an uint_16 | |
| uint16_t to_u16() const { return to<uint16_t>(); } | |
| // converts the value whole signal at this time to an uint_32 | |
| uint32_t to_u32() const { return to<uint32_t>(); } | |
| // converts the value whole signal at this time to an uint_64 | |
| uint64_t to_u64() const { return to<uint64_t>(); } | |
| void set_to(unsigned long long value) { | |
| for (size_t i = 0; i < N; ++i) { | |
| (*this)[i] = (value >> i) & 1; | |
| } | |
| } | |
| }; | |
| template<size_t N> | |
| std::ostream& operator<<(std::ostream& os, const Signal<N>& s) { | |
| os << "b"; | |
| for (size_t i = N; i > 0; --i) { | |
| os << s.get_value(i - 1) ? "1" : "."; | |
| } | |
| return os; | |
| } | |
| } // namespace signal |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment