// license:BSD-3-Clause // copyright-holders:Couriersud /* * nld_74123.cpp * * 74123: Dual Retriggerable One-Shot with Clear and Complementary Outputs * * +--------------+ * A1 |1 ++ 16| VCC * B1 |2 15| RC1 * CLR1 |3 14| C1 * Q1Q |4 74123 13| Q1 * Q2 |5 12| Q2Q * C2 |6 11| CLR2 * RC2 |7 10| B2 * GND |8 9| A2 * +--------------+ * * Naming conventions follow Fairchild Semiconductor datasheet * * DM9602: Dual Retriggerable, Resettable One Shots * * +--------------+ * C1 |1 ++ 16| VCC * RC1 |2 15| C2 * CLR1 |3 14| RC2 * B1 |4 9602 13| CLR2 * A1 |5 12| B2 * Q1 |6 11| A2 * Q1Q |7 10| Q2 * GND |8 9| Q2Q * +--------------+ * * CD4538: Dual Retriggerable, Resettable One Shots * * +--------------+ * C1 |1 ++ 16| VCC * RC1 |2 15| C2 * CLR1 |3 14| RC2 * A1 |4 4538 13| CLR2 * B1 |5 12| A2 * Q1 |6 11| B2 * Q1Q |7 10| Q2 * GND |8 9| Q2Q * +--------------+ * */ #include "analog/nlid_twoterm.h" #include "nlid_system.h" namespace netlist::devices { /// \brief Base monostable device /// /// The basic operation is the following: /// /// After a trigger signal has been detected, the output changes to high. /// The capacitor is quickly discharged until it reaches Vlow. /// The device toggles into charging mode until Vhigh is reached. At this /// point, the output is set to low. Charging continues until VCC - or /// more specific VRC is reached. /// /// Using /// /// - Vlow = alpha * VCC /// - Vhigh = (1-alpha) * VCC /// - Ignoring Rext during discharge /// /// we have calculate the following time constants: /// /// - tD = - X*C * ln(alpha) /// - tC = - R*C * ln(alhpa) /// - tL = - R*C * ln(1.0 - alpha) /// /// where tD denotes the time to discharge from VCC to Vlow, tC denotes /// the time to charge from 0.0 to Vhigh and tL denotes the time to charge /// from 0 to Vlow. X denotes the internal resistance used to discharge the /// capacitor. The total impulse duration is thus /// /// tP = tD + tC - tL /// /// Using K = ln(1 - alpha) - ln (alpha) = ln(1/alpha - 1) /// /// we get /// /// tP = R*C*K * (1 + X/R * ln(alpha) / K) /// /// and alpha = 1 / (1 + exp(K)) /// /// Datasheets express the right term usually as /// /// (1 + f * 1000 / R) = (1 + X/R * ln(alpha) / K) /// /// and thus /// /// X = f * 1000 * K / ln(alpha) /// /// FIXME: Currently X is given directly (as RI). It would be better to use /// f (usually 0.7) and K to calculate X. /// template NETLIB_OBJECT(74123_base) { NETLIB_CONSTRUCTOR(74123_base) , m_RP(*this, "RP") , m_RN(*this, "RN") , m_RP_Q(*this, "_RP_Q") , m_RN_Q(*this, "_RN_Q") , m_I(*this, D::names(), NETLIB_DELEGATE(ab_clear)) , m_Q(*this, "Q") , m_QQ(*this, "QQ") , m_CV(*this, "_CV", NETLIB_DELEGATE(cv)) // internal , m_last_trig(*this, "m_last_trig", 0) , m_state(*this, "m_state", 0) , m_KP(plib::reciprocal(nlconst::one() + plib::exp(D::K()))) //, m_power_pins(*this) { register_sub_alias(pstring(D::gnd()), "RN.2"); register_sub_alias(pstring(D::vcc()), "RP.1"); register_sub_alias("C", "RN.2"); register_sub_alias("RC", "RN.1"); connect("_RP_Q", "RP.I"); connect("_RN_Q", "RN.I"); connect("RN.1", "RP.2"); connect("_CV", "RN.1"); m_RP().m_RON.set(D::RI()); m_RN().m_RON.set(D::RI()); } NETLIB_HANDLERI(ab_clear) { netlist_sig_t m_trig(0); m_trig = D::trigfunc(m_I); if (!D::clear(m_I)) { m_Q.push(0, D::t_C_to_Q::value()); m_QQ.push(1, D::t_C_to_Q::value()); /* quick charge until trigger */ /* FIXME: SGS datasheet shows quick charge to 5V, * though schematics indicate quick charge to Vhigh only. */ m_RP_Q.push(1, D::t_C_to_Q::value()); // R_ON m_RN_Q.push(0, D::t_C_to_Q::value()); // R_OFF m_state = 2; //charging (quick) } else if (!m_last_trig && m_trig) { // FIXME: Timing! m_Q.push(1, D::t_AB_to_Q::value()); m_QQ.push(0, D::t_AB_to_Q::value()); m_RN_Q.push(1, D::t_AB_to_Q::value()); // R_ON m_RP_Q.push(0, D::t_AB_to_Q::value()); // R_OFF m_state = 1; // discharging } m_last_trig = m_trig; } NETLIB_HANDLERI(cv) { if (m_state == 1) { const nl_fptype vLow = m_KP * m_RP().P()(); if (m_CV() < vLow) { m_RN_Q.push(0, NLTIME_FROM_NS(10)); // R_OFF m_state = 2; // charging } } if (m_state == 2) { const nl_fptype vHigh = (nlconst::one() - m_KP) * m_RP().P()(); if (m_CV() > vHigh) { m_RP_Q.push(0, NLTIME_FROM_NS(10)); // R_OFF m_Q.push(0, NLTIME_FROM_NS(10)); m_QQ.push(1, NLTIME_FROM_NS(10)); m_state = 0; // waiting } } } NETLIB_RESETI() { m_RP().reset(); m_RN().reset(); //m_RP.set_R(R_OFF); //m_RN.set_R(R_OFF); m_last_trig = 0; m_state = 0; } private: NETLIB_SUB(sys_dsw1) m_RP; NETLIB_SUB(sys_dsw1) m_RN; logic_output_t m_RP_Q; logic_output_t m_RN_Q; object_array_t m_I; logic_output_t m_Q; logic_output_t m_QQ; analog_input_t m_CV; state_var m_last_trig; state_var m_state; nl_fptype m_KP; //nld_power_pins m_power_pins; // not needed, device exposes VCC and GND }; struct desc_74123 : public desc_base { using t_AB_to_Q = time_ns<10>; using t_C_to_Q = time_ns<10>; static constexpr nl_fptype K() { return nlconst::magic(0.4); } static constexpr nl_fptype RI() { return nlconst::magic(400.0); } static constexpr std::array names() { return {"CLRQ", "A", "B"};} template static netlist_sig_t trigfunc(const T &in) { return ((in[1]() | (in[2]() ^ 1)) ^ 1) & in[0](); // ((m_A() | (m_B() ^ 1)) ^ 1) & m_CLRQ() } template static constexpr netlist_sig_t clear(const T &in) { return in[0]();} static constexpr const char *vcc() { return "VCC"; } static constexpr const char *gnd() { return "GND"; } }; struct desc_74121 : public desc_74123 { static constexpr nl_fptype K() { return nlconst::magic(0.7); } static constexpr std::array names() { return {"A1", "A2", "B"};} template static netlist_sig_t trigfunc(const T &in) { return ((in[0]() ^ 1) | (in[1]() ^ 1)) & in[2](); // (~A1 | ~A2) & B } template static constexpr netlist_sig_t clear([[maybe_unused]] const T &in) { return 1; } }; struct desc_9602 : public desc_74123 { template static netlist_sig_t trigfunc(const T &in) { return ((in[1]() ^ 1) | in[2]()); // (m_A() ^ 1) | m_B() } }; struct desc_4538 : public desc_74123 { using t_AB_to_Q = time_ns<300>; using t_C_to_Q = time_ns<250>; static constexpr nl_fptype K() { return nlconst::one(); } // CD4538 datasheet states PW=RC template static netlist_sig_t trigfunc(const T &in) { return (in[1]() | (in[2]() ^ 1)); // m_A() | (m_B() ^ 1) } static constexpr const char *vcc() { return "VDD"; } static constexpr const char *gnd() { return "VSS"; } }; using NETLIB_NAME(74123) = NETLIB_NAME(74123_base); using NETLIB_NAME(74121) = NETLIB_NAME(74123_base); using NETLIB_NAME(4538) = NETLIB_NAME(74123_base); using NETLIB_NAME(9602) = NETLIB_NAME(74123_base); NETLIB_DEVICE_IMPL(74123, "TTL_74123", "") NETLIB_DEVICE_IMPL(74121, "TTL_74121", "") NETLIB_DEVICE_IMPL(4538, "CD4538", "") NETLIB_DEVICE_IMPL(9602, "TTL_9602", "") } // namespace netlist::devices