initial commit | complete a draft outline
This commit is contained in:
40
demo/demo.cpp
Normal file
40
demo/demo.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include "../include/tools"
|
||||
|
||||
struct Input {
|
||||
Wire a;
|
||||
Wire b;
|
||||
std::array <Wire, 2> c;
|
||||
};
|
||||
|
||||
struct Output {
|
||||
Register d;
|
||||
Register e;
|
||||
};
|
||||
|
||||
struct Content {
|
||||
Register r;
|
||||
};
|
||||
|
||||
struct MyModule : Input, Output, private Content {
|
||||
using Tags = SyncTags <Input, Output, Content>;
|
||||
friend class Visitor;
|
||||
|
||||
void demo() {
|
||||
this->d <= this->a + this->b;
|
||||
}
|
||||
};
|
||||
|
||||
signed main() {
|
||||
MyModule m;
|
||||
|
||||
m.a = []() { return 1; };
|
||||
m.b.assign([&]() { return (target_size_t)m.d; });
|
||||
|
||||
for (int i = 0 ; i < 10 ; ++i) {
|
||||
std::cout << m.d << std::endl;
|
||||
m.demo();
|
||||
std::cout << m.d << std::endl;
|
||||
sync_member(m);
|
||||
}
|
||||
return 0;
|
||||
}
|
61
include/debug.h
Normal file
61
include/debug.h
Normal file
@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
#include <source_location>
|
||||
|
||||
namespace dark::debug {
|
||||
|
||||
/**
|
||||
* Copied from https://en.cppreference.com/w/cpp/utility/unreachable
|
||||
* If compiled under C++23, just use std::unreachable() instead.
|
||||
*/
|
||||
[[noreturn]] inline void unreachable() {
|
||||
#if __cplusplus > 202002L
|
||||
std::unreachable();
|
||||
#elif defined(_MSC_VER) && !defined(__clang__) // MSVC
|
||||
// Uses compiler specific extensions if possible.
|
||||
// Even if no extension is used, undefined behavior is still raised by
|
||||
// an empty function body and the noreturn attribute.
|
||||
__assume(false);
|
||||
#else // GCC, Clang
|
||||
__builtin_unreachable();
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename _Tp, typename... _Args>
|
||||
struct assert {
|
||||
explicit assert(_Tp &&condition, _Args &&...args,
|
||||
std::source_location location = std::source_location::current()) {
|
||||
if (condition) return;
|
||||
std::cerr << "Assertion failed at: "
|
||||
<< location.file_name() << ":" << location.line() << "\n";
|
||||
if constexpr (sizeof...(args) != 0) {
|
||||
std::cerr << "Message: ";
|
||||
((std::cerr << args), ...) << std::endl;
|
||||
}
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename _Tp, typename... _Args>
|
||||
assert(_Tp &&, _Args &&...) -> assert<_Tp, _Args...>;
|
||||
|
||||
template <typename _Tp, _Tp _Default>
|
||||
struct DebugValue {
|
||||
#ifdef _DEBUG
|
||||
private:
|
||||
_Tp _M_value = _Default;
|
||||
public:
|
||||
auto get_value() const { return this->_M_value; }
|
||||
auto set_value(_Tp value) { this->_M_value = value; }
|
||||
#else
|
||||
public:
|
||||
auto get_value() const { return _Default; }
|
||||
auto set_value(_Tp) { /* do nothing */ }
|
||||
#endif
|
||||
public:
|
||||
explicit operator _Tp() const { return this->get_value(); }
|
||||
DebugValue &operator=(_Tp value) { this->set_value(value); return *this; }
|
||||
};
|
||||
|
||||
} // namespace dark::debug
|
138
include/hardware.h
Normal file
138
include/hardware.h
Normal file
@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
#include "debug.h"
|
||||
#include "target.h"
|
||||
#include <memory>
|
||||
#include <concepts>
|
||||
|
||||
namespace dark::hardware {
|
||||
|
||||
struct Visitor;
|
||||
struct Wire;
|
||||
struct Register;
|
||||
|
||||
template <typename _Fn>
|
||||
concept WireFunction =
|
||||
!std::same_as <Wire, std::decay_t <_Fn>> &&
|
||||
requires(_Fn &&__f) { { __f() } -> std::convertible_to <target_size_t>; };
|
||||
|
||||
struct WireBase {
|
||||
using _Ret_t = int;
|
||||
using _Cpy_t = WireBase *;
|
||||
virtual _Ret_t call() const = 0;
|
||||
virtual ~WireBase() = default;
|
||||
};
|
||||
|
||||
template <WireFunction _Fn>
|
||||
struct WireImpl final : WireBase {
|
||||
_Fn _M_lambda;
|
||||
|
||||
template <typename _Fn2>
|
||||
WireImpl(_Fn2 &&fn) : _M_lambda(std::forward <_Fn2>(fn)) {}
|
||||
|
||||
_Ret_t call() const override { return this->_M_lambda(); }
|
||||
};
|
||||
|
||||
struct EmptyWire final : WireBase {
|
||||
_Ret_t call() const override {
|
||||
debug::assert(false, "Empty wire is called.");
|
||||
debug::unreachable();
|
||||
}
|
||||
};
|
||||
|
||||
struct Wire {
|
||||
private:
|
||||
friend struct Visitor;
|
||||
|
||||
using _Manage_t = WireBase;
|
||||
|
||||
std::unique_ptr <_Manage_t> _M_impl;
|
||||
mutable target_size_t _M_cache;
|
||||
mutable bool _M_holds;
|
||||
|
||||
[[no_unique_address]]
|
||||
debug::DebugValue <bool, false> _M_dirty; // Can be assigned only once
|
||||
|
||||
void sync() { this->_M_holds = false; }
|
||||
|
||||
template <WireFunction _Fn>
|
||||
static auto _M_new_impl(_Fn &&fn) -> _Manage_t * {
|
||||
return new WireImpl <std::decay_t<_Fn>> {std::forward <_Fn>(fn)};
|
||||
}
|
||||
|
||||
void _M_check_double_assign() {
|
||||
debug::assert(!this->_M_dirty, "Wire is already assigned.");
|
||||
this->_M_dirty = true;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Wire() : _M_impl(new EmptyWire), _M_cache(), _M_holds(), _M_dirty() {}
|
||||
|
||||
Wire(Wire &&) = delete;
|
||||
Wire(const Wire &) = delete;
|
||||
Wire &operator=(Wire &&) = delete;
|
||||
Wire &operator=(const Wire &rhs) = delete;
|
||||
|
||||
template <WireFunction _Fn>
|
||||
Wire(_Fn &&fn)
|
||||
: _M_impl(_M_new_impl(std::forward <_Fn>(fn))), _M_cache(), _M_holds(), _M_dirty() {}
|
||||
|
||||
template <WireFunction _Fn>
|
||||
Wire &operator=(_Fn &&fn) {
|
||||
this->assign(std::forward <_Fn>(fn));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <WireFunction _Fn>
|
||||
void assign(_Fn &&fn) {
|
||||
this->_M_check_double_assign();
|
||||
this->_M_impl.reset(this->_M_new_impl(std::forward <_Fn>(fn)));
|
||||
this->_M_holds = false;
|
||||
}
|
||||
|
||||
operator target_size_t() const {
|
||||
if (this->_M_holds == false) {
|
||||
this->_M_cache = this->_M_impl->call();
|
||||
this->_M_holds = true;
|
||||
}
|
||||
return this->_M_cache;
|
||||
}
|
||||
};
|
||||
|
||||
struct Register {
|
||||
private:
|
||||
friend struct Visitor;
|
||||
|
||||
target_size_t _M_new;
|
||||
target_size_t _M_old;
|
||||
|
||||
[[no_unique_address]]
|
||||
debug::DebugValue <bool, false> _M_dirty;
|
||||
|
||||
void sync() {
|
||||
if (this->_M_dirty) {
|
||||
this->_M_old = this->_M_new;
|
||||
this->_M_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void set_value(target_size_t value) {
|
||||
this->_M_new = value;
|
||||
debug::assert(!this->_M_dirty, "Register is already assigned in this cycle.");
|
||||
this->_M_dirty = true;
|
||||
}
|
||||
|
||||
auto get_value() const -> target_size_t { return this->_M_old; }
|
||||
|
||||
public:
|
||||
Register() : _M_new(), _M_old(), _M_dirty() {}
|
||||
|
||||
template <std::convertible_to <target_size_t> _Int>
|
||||
void operator <= (_Int &&value) {
|
||||
this->set_value(static_cast <target_size_t>(value));
|
||||
}
|
||||
|
||||
operator target_size_t() const { return this->get_value(); }
|
||||
};
|
||||
|
||||
} // namespace dark::hardware
|
81
include/reflect.h
Normal file
81
include/reflect.h
Normal file
@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
#include <tuple>
|
||||
#include <concepts>
|
||||
|
||||
namespace dark::reflect {
|
||||
|
||||
/* A init helper to get the size of a struct. */
|
||||
struct init_helper { template <typename _Tp> operator _Tp(); };
|
||||
|
||||
/* A size helper to get the size of a struct. */
|
||||
template <typename _Tp> requires std::is_aggregate_v <_Tp>
|
||||
inline consteval auto member_size_aux(auto &&...args) -> std::size_t {
|
||||
constexpr std::size_t size = sizeof...(args);
|
||||
constexpr std::size_t maximum = 114;
|
||||
if constexpr (size > maximum) {
|
||||
static_assert (sizeof(_Tp) == 0, "The struct has too many members.");
|
||||
} else if constexpr (!requires {_Tp { args... }; }) {
|
||||
return size - 1;
|
||||
} else {
|
||||
return member_size_aux <_Tp> (args..., init_helper {});
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the member size for a aggregate type without base. */
|
||||
template <typename _Tp> requires std::is_aggregate_v <_Tp>
|
||||
inline consteval auto member_size(_Tp &) -> std::size_t { return member_size_aux <_Tp> (); }
|
||||
|
||||
template <typename _Tp> requires std::is_aggregate_v <_Tp>
|
||||
inline consteval auto member_size() -> std::size_t { return member_size_aux <_Tp> (); }
|
||||
|
||||
template <typename _Tp> requires std::is_aggregate_v <_Tp>
|
||||
auto tuplify(_Tp &value) {
|
||||
constexpr auto size = member_size <_Tp> ();
|
||||
if constexpr (size == 1) {
|
||||
auto &[x0] = value;
|
||||
return std::forward_as_tuple(x0);
|
||||
} else if constexpr (size == 2) {
|
||||
auto &[x0, x1] = value;
|
||||
return std::forward_as_tuple(x0, x1);
|
||||
} else if constexpr (size == 3) {
|
||||
auto &[x0, x1, x2] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2);
|
||||
} else if constexpr (size == 4) {
|
||||
auto &[x0, x1, x2, x3] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3);
|
||||
} else if constexpr (size == 5) {
|
||||
auto &[x0, x1, x2, x3, x4] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4);
|
||||
} else if constexpr (size == 6) {
|
||||
auto &[x0, x1, x2, x3, x4, x5] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5);
|
||||
} else if constexpr (size == 7) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6);
|
||||
} else if constexpr (size == 8) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7);
|
||||
} else if constexpr (size == 9) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7, x8] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7, x8);
|
||||
} else if constexpr (size == 10) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7, x8, x9] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9);
|
||||
} else if constexpr (size == 11) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10);
|
||||
} else if constexpr (size == 12) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11);
|
||||
} else if constexpr (size == 13) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12);
|
||||
} else if constexpr (size == 14) {
|
||||
auto &[x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13] = value;
|
||||
return std::forward_as_tuple(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13);
|
||||
} else {
|
||||
static_assert (sizeof(_Tp) == 0, "The struct has too many members.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dark::reflect
|
59
include/synchronize.h
Normal file
59
include/synchronize.h
Normal file
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include "hardware.h"
|
||||
#include "reflect.h"
|
||||
#include <array>
|
||||
|
||||
namespace dark::hardware {
|
||||
|
||||
struct Visitor {
|
||||
template <typename _Tp>
|
||||
static constexpr bool is_syncable_v =
|
||||
requires(_Tp &val) { { val.sync() } -> std::same_as<void>; };
|
||||
|
||||
template <typename _Tp> requires is_syncable_v<_Tp>
|
||||
static void sync(_Tp &val) { val.sync(); }
|
||||
|
||||
template <typename _Tp, typename _Base>
|
||||
static _Base &cast(_Tp &value) { return static_cast<_Base &>(value); }
|
||||
};
|
||||
|
||||
template <typename ..._Base>
|
||||
struct SyncTags {};
|
||||
|
||||
template <typename _Tp>
|
||||
static constexpr bool is_valid_tag_v = false;
|
||||
template <typename ..._Base>
|
||||
static constexpr bool is_valid_tag_v<SyncTags<_Base...>> = true;
|
||||
template <typename _Tp>
|
||||
concept has_valid_tag = is_valid_tag_v<typename _Tp::Tags>;
|
||||
|
||||
template <typename _Tp>
|
||||
static constexpr bool is_std_array_v = std::is_array_v<_Tp>;
|
||||
template <typename _Tp, std::size_t _Nm>
|
||||
static constexpr bool is_std_array_v<std::array<_Tp, _Nm>> = true;
|
||||
|
||||
template <typename _Tp>
|
||||
inline void sync_member(_Tp &value);
|
||||
|
||||
template <typename _Tp, typename ..._Base>
|
||||
inline void sync_by_tag(_Tp &value, SyncTags<_Base...>) {
|
||||
(sync_member(Visitor::cast<_Tp, _Base>(value)), ...);
|
||||
}
|
||||
|
||||
template <typename _Tp>
|
||||
inline void sync_member(_Tp &value) {
|
||||
if constexpr (is_std_array_v<_Tp>) {
|
||||
for (auto &member : value) sync_member(member);
|
||||
} else if constexpr (Visitor::is_syncable_v<_Tp>) {
|
||||
Visitor::sync(value);
|
||||
} else if constexpr (has_valid_tag<_Tp>) {
|
||||
sync_by_tag(value, typename _Tp::Tags {});
|
||||
} else if constexpr (std::is_aggregate_v<_Tp>) {
|
||||
auto &&tuple = reflect::tuplify(value);
|
||||
std::apply([](auto &...members) { (sync_member(members), ...); }, tuple);
|
||||
} else {
|
||||
static_assert(sizeof(_Tp) == 0, "This type is not syncable.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dark::hardware
|
9
include/target.h
Normal file
9
include/target.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace dark {
|
||||
|
||||
using target_size_t = std::uint32_t;
|
||||
using target_ssize_t = std::int32_t;
|
||||
|
||||
} // namespace dark
|
0
include/template.h
Normal file
0
include/template.h
Normal file
Reference in New Issue
Block a user