// license:GPL-2.0+ // copyright-holders:Couriersud /// /// \file nets.h /// #ifndef NL_CORE_NETS_H_ #define NL_CORE_NETS_H_ #include "base_objects.h" #include "core_device.h" #include "exec.h" #include "state_var.h" #include "../nltypes.h" #include "../plib/plists.h" #include "../plib/pstring.h" // Enable the setting below to avoid queue pushes were at execution // no action will be taken. This is academically cleaner, but slower than // allowing this to happen and filter it during during "process". #define AVOID_NOOP_QUEUE_PUSHES (0) namespace netlist { namespace detail { // ----------------------------------------------------------------------------- // net_t // ----------------------------------------------------------------------------- class net_t : public netlist_object_t { public: enum class queue_status { DELAYED_DUE_TO_INACTIVE = 0, QUEUED, DELIVERED }; net_t(netlist_state_t &nl, const pstring &aname, core_terminal_t *railterminal = nullptr); PCOPYASSIGNMOVE(net_t, delete) virtual ~net_t() noexcept = default; virtual void reset() noexcept; // ----------------------------------------------------------------------------- // Hot section // // Any changes below will impact performance. // ----------------------------------------------------------------------------- void toggle_new_Q() noexcept { m_new_Q = (m_cur_Q ^ 1); } void toggle_and_push_to_queue(const netlist_time &delay) noexcept { toggle_new_Q(); push_to_queue(delay); } void push_to_queue(const netlist_time &delay) noexcept { if (has_connections()) { if (!!is_queued()) exec().qremove(this); m_next_scheduled_time = exec().time() + delay; #if (AVOID_NOOP_QUEUE_PUSHES) m_in_queue = (m_list_active.empty() ? queue_status::DELAYED_DUE_TO_INACTIVE : (m_new_Q != m_cur_Q ? queue_status::QUEUED : queue_status::DELIVERED)); if (m_in_queue == queue_status::QUEUED) exec().qpush(m_next_scheduled_time, this); else update_inputs(); #else m_in_queue = m_list_active.empty() ? queue_status::DELAYED_DUE_TO_INACTIVE : queue_status::QUEUED; if (m_in_queue == queue_status::QUEUED) exec().qpush(m_next_scheduled_time, this); else update_inputs(); #endif } } bool is_queued() const noexcept { return m_in_queue == queue_status::QUEUED; } // ----------------------------------------------------------------------------- // Very hot // ----------------------------------------------------------------------------- template void update_devs() noexcept { gsl_Expects(this->is_rail_net()); m_in_queue = queue_status::DELIVERED; // mark as taken ... const netlist_sig_t new_Q(m_new_Q); const netlist_sig_t cur_Q(m_cur_Q); #if (!AVOID_NOOP_QUEUE_PUSHES) if (new_Q ^ cur_Q) #endif { const auto mask = (new_Q << core_terminal_t::INP_LH_SHIFT) | (cur_Q << core_terminal_t::INP_HL_SHIFT); m_cur_Q = new_Q; if (!KEEP_STATS) { for (auto &p : m_list_active) { p.set_copied_input(new_Q); if ((p.terminal_state() & mask) != 0) p.run_delegate(); } } else { for (auto & p : m_list_active) { p.set_copied_input(new_Q); auto *stats(p.device().stats()); stats->m_stat_call_count.inc(); if ((p.terminal_state() & mask)) { auto g(stats->m_stat_total_time.guard()); p.run_delegate(); } } } } } const netlist_time_ext &next_scheduled_time() const noexcept { return m_next_scheduled_time; } void set_next_scheduled_time(netlist_time_ext ntime) noexcept { m_next_scheduled_time = ntime; } bool is_rail_net() const noexcept { return !(m_railterminal == nullptr); } core_terminal_t & railterminal() const noexcept { return *m_railterminal; } bool has_connections() const noexcept { return !m_core_terms.empty(); } void add_to_active_list(core_terminal_t &term) noexcept { if (!m_list_active.empty()) { term.set_copied_input(m_cur_Q); m_list_active.push_front(&term); } else { m_list_active.push_front(&term); railterminal().device().do_inc_active(); if (m_in_queue == queue_status::DELAYED_DUE_TO_INACTIVE) { #if (AVOID_NOOP_QUEUE_PUSHES) if (m_next_scheduled_time > exec().time() && (m_cur_Q != m_new_Q)) #else if (m_next_scheduled_time > exec().time()) #endif { m_in_queue = queue_status::QUEUED; // pending exec().qpush(m_next_scheduled_time, this); } else { m_in_queue = queue_status::DELIVERED; m_cur_Q = m_new_Q; } update_inputs(); } else term.set_copied_input(m_cur_Q); } } void remove_from_active_list(core_terminal_t &term) noexcept { gsl_Expects(!m_list_active.empty()); m_list_active.remove(&term); if (m_list_active.empty()) { #if (AVOID_NOOP_QUEUE_PUSHES) if (!!is_queued()) { exec().qremove(this); m_in_queue = queue_status::DELAYED_DUE_TO_INACTIVE; } #endif railterminal().device().do_dec_active(); } } // ----------------------------------------------------------------------------- // setup stuff - cold // ----------------------------------------------------------------------------- bool is_logic() const noexcept; bool is_analog() const noexcept; void rebuild_list(); // rebuild m_list after a load std::vector &core_terms() noexcept { return m_core_terms; } void update_inputs() noexcept { #if NL_USE_COPY_INSTEAD_OF_REFERENCE for (auto & term : m_core_terms) term->m_Q = m_cur_Q; #endif // nothing needs to be done if define not set } protected: // only used for logic nets const netlist_sig_t &Q() const noexcept { return m_cur_Q; } // only used for logic nets void initial(netlist_sig_t val) noexcept { m_cur_Q = m_new_Q = val; update_inputs(); } // only used for logic nets void set_Q_and_push(const netlist_sig_t &newQ, const netlist_time &delay) noexcept { gsl_Expects(delay >= netlist_time::zero()); if (newQ != m_new_Q) { m_new_Q = newQ; push_to_queue(delay); } } // only used for logic nets void set_Q_time(const netlist_sig_t &newQ, const netlist_time_ext &at) noexcept { gsl_Expects(at >= netlist_time_ext::zero()); if (newQ != m_new_Q) { m_in_queue = queue_status::DELAYED_DUE_TO_INACTIVE; m_next_scheduled_time = at; m_cur_Q = m_new_Q = newQ; update_inputs(); } else { m_cur_Q = newQ; update_inputs(); } } private: state_var m_new_Q; state_var m_cur_Q; state_var m_in_queue; plib::linkedlist_t m_list_active; state_var m_next_scheduled_time; core_terminal_t * m_railterminal; std::vector m_core_terms; // save post-start m_list ... }; } // namespace detail class analog_net_t : public detail::net_t { public: analog_net_t(netlist_state_t &nl, const pstring &aname, detail::core_terminal_t *railterminal = nullptr); void reset() noexcept override; const nl_fptype &Q_Analog() const noexcept { return m_cur_Analog; } void set_Q_Analog(nl_fptype v) noexcept { m_cur_Analog = v; } // used by solver code ... nl_fptype *Q_Analog_state_ptr() noexcept { return *m_cur_Analog; } //FIXME: needed by current solver code solver::matrix_solver_t *solver() const noexcept { return m_solver; } void set_solver(solver::matrix_solver_t *solver) noexcept { m_solver = solver; } friend constexpr bool operator==(const analog_net_t &lhs, const analog_net_t &rhs) noexcept { return &lhs == &rhs; } private: state_var m_cur_Analog; solver::matrix_solver_t *m_solver; }; class logic_net_t : public detail::net_t { public: logic_net_t(netlist_state_t &nl, const pstring &aname, detail::core_terminal_t *railterminal = nullptr); using detail::net_t::Q; using detail::net_t::initial; using detail::net_t::set_Q_and_push; using detail::net_t::set_Q_time; }; } // namespace netlist #endif // NL_CORE_NETS_H_