Skip to content

Instantly share code, notes, and snippets.

@m516
Created August 4, 2025 16:25
Show Gist options
  • Select an option

  • Save m516/20c2778d2b9f6ebe2a54f05b75e8c3a6 to your computer and use it in GitHub Desktop.

Select an option

Save m516/20c2778d2b9f6ebe2a54f05b75e8c3a6 to your computer and use it in GitHub Desktop.
Inefficient but flexible digital signal grouping
#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