summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/netlist/devices/nld_74ls629.cpp
blob: fd8f424fa3252444934ea0551a731623ec0209ac (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// license:GPL-2.0+
// copyright-holders:Couriersud
/*
 * nld_SN74LS629.c
 *
 */

/*
 * The 74LS624 series are constant current based VCOs.  The Freq Control voltage
 * modulates the current source.  The current is created from Rext, which is
 * internally fixed at 600 ohms for all devices except the 74LS628 which has
 * external connections.  The current source linearly discharges the cap voltage.
 * The cap starts with 0V charge across it.  One side is connected to a fixed voltage
 * bias circuit.  The other side is charged negatively from the current source until
 * a certain low threshold is reached.  Once this threshold is reached, the output
 * toggles state and the pins on the cap reverse in respect to the charge/bias hookup.
 * This starts the one side of the cap to be at bias, and the other side of the cap is
 * now at bias + the charge on the cap which is bias - threshold.
 * Y = 0;  CX1 = bias;    CX2 = charge
 * Y = 1;  CX1 = charge;  CX2 = bias
 * The Range voltage adjusts the threshold voltage.  The higher the Range voltage,
 * the lower the threshold voltage, the longer the cap can charge, the lower the frequency.
 *
 * In a perfect world it would work like this:
 * The current is based on the mysterious Rext mentioned in the data sheet.
 * I = (VfreqControl  * 20k/90k) / Rext
 * where Rext = 600 ohms or external Rext on a 74LS628
 * The Freq Control has an input impedance of approximately 90k, so any input resistance
 * connected to the Freq Control pin works as a voltage divider.
 * I = (VfreqControl * 20k/(90k + RfreqControlIn)) / Rext
 * That gives us a change in voltage on the cap of
 * dV = I / sampleRate / C_inFarads
 *
 * Unfortunately the chip does not behave linearly do to internal interactions,
 * so I have just worked out the formula (using zunzun.com) of FreqControl and
 * range to frequency out for a fixed cap value of 0.1uf.  Other cap values can just
 * scale from that.  From the freq, we calculate the time of 1/2 cycle using 1/Freq/2.
 * Then just use that to toggle a waveform.
 */


#include "nld_74ls629.h"
#include "netlist/analog/nlid_twoterm.h"

namespace netlist
{
	namespace devices
	{
	NETLIB_OBJECT(SN74LS629clk)
	{
		NETLIB_CONSTRUCTOR(SN74LS629clk)
		, m_FB(*this, "FB")
		, m_Y(*this, "Y")
		, m_enableq(*this, "m_enableq", 1)
		, m_out(*this, "m_out", 0)
		, m_inc(*this, "m_inc", netlist_time::zero())
		{
			connect(m_FB, m_Y);
		}

		NETLIB_RESETI()
		{
			m_enableq = 0;
			m_out = 0;
			m_inc = netlist_time::zero();
		}

		NETLIB_UPDATEI();

	public:
		logic_input_t m_FB;
		logic_output_t m_Y;

		state_var<netlist_sig_t> m_enableq;
		state_var<netlist_sig_t> m_out;
		state_var<netlist_time> m_inc;
	};

	NETLIB_OBJECT(SN74LS629)
	{
		NETLIB_CONSTRUCTOR(SN74LS629)
		, m_clock(*this, "OSC")
		, m_R_FC(*this, "R_FC")
		, m_R_RNG(*this, "R_RNG")
		, m_ENQ(*this, "ENQ")
		, m_RNG(*this, "RNG")
		, m_FC(*this, "FC")
		, m_CAP(*this, "CAP", 1e-6)
		{
			register_subalias("GND",    m_R_FC.m_N);

			connect(m_FC, m_R_FC.m_P);
			connect(m_RNG, m_R_RNG.m_P);
			connect(m_R_FC.m_N, m_R_RNG.m_N);

			register_subalias("Y", m_clock.m_Y);
		}

		NETLIB_RESETI()
		{
			m_R_FC.set_R(90000.0);
			m_R_RNG.set_R(90000.0);
			m_clock.reset();
		}
		NETLIB_UPDATEI();

		NETLIB_UPDATE_PARAMI()
		{
			/* update param may be called from anywhere, update_dev(time) is not a good idea */
		}

	public:
		NETLIB_SUB(SN74LS629clk) m_clock;
		analog::NETLIB_SUB(R_base) m_R_FC;
		analog::NETLIB_SUB(R_base) m_R_RNG;

		logic_input_t m_ENQ;
		analog_input_t m_RNG;
		analog_input_t m_FC;

		param_double_t m_CAP;
	};

	NETLIB_OBJECT(SN74LS629_dip)
	{
		NETLIB_CONSTRUCTOR(SN74LS629_dip)
		, m_A(*this, "A")
		, m_B(*this, "B")
		{
			register_subalias("1",  m_B.m_FC);
			register_subalias("2",  m_A.m_FC);
			register_subalias("3",  m_A.m_RNG);

			register_subalias("6",  m_A.m_ENQ);
			register_subalias("7",  m_A.m_clock.m_Y);

			register_subalias("8",  m_A.m_R_FC.m_N);
			register_subalias("9",  m_A.m_R_FC.m_N);
			connect(m_A.m_R_FC.m_N, m_B.m_R_FC.m_N);

			register_subalias("10",  m_B.m_clock.m_Y);

			register_subalias("11",  m_B.m_ENQ);
			register_subalias("14",  m_B.m_RNG);
		}

		NETLIB_UPDATEI() { }

		NETLIB_RESETI()
		{
			m_A.reset();
			m_B.reset();
		}

	private:
		NETLIB_SUB(SN74LS629) m_A;
		NETLIB_SUB(SN74LS629) m_B;
	};


	NETLIB_UPDATE(SN74LS629clk)
	{
		if (!m_enableq)
		{
			m_out = m_out ^ 1;
			m_Y.push(m_out, m_inc);
		}
		else
		{
			m_Y.push(1, m_inc);
		}
	}

	NETLIB_UPDATE(SN74LS629)
	{
		{
			// recompute
			nl_double  v_freq = m_FC();
			nl_double  v_rng = m_RNG();

			/* coefficients */
			const nl_double k1 = 1.9904769024796283E+03;
			const nl_double k2 = 1.2070059213983407E+03;
			const nl_double k3 = 1.3266985579561108E+03;
			const nl_double k4 = -1.5500979825922698E+02;
			const nl_double k5 = 2.8184536266938172E+00;
			const nl_double k6 = -2.3503421582744556E+02;
			const nl_double k7 = -3.3836786704527788E+02;
			const nl_double k8 = -1.3569136703258670E+02;
			const nl_double k9 = 2.9914575453819188E+00;
			const nl_double k10 = 1.6855569086173170E+00;

			/* scale due to input resistance */

			/* Polyfunctional3D_model created by zunzun.com using sum of squared absolute error */

			nl_double v_freq_2 = v_freq * v_freq;
			nl_double v_freq_3 = v_freq_2 * v_freq;
			nl_double v_freq_4 = v_freq_3 * v_freq;
			nl_double freq = k1;
			freq += k2 * v_freq;
			freq += k3 * v_freq_2;
			freq += k4 * v_freq_3;
			freq += k5 * v_freq_4;
			freq += k6 * v_rng;
			freq += k7 * v_rng * v_freq;
			freq += k8 * v_rng * v_freq_2;
			freq += k9 * v_rng * v_freq_3;
			freq += k10 * v_rng * v_freq_4;

			freq *= plib::constants<nl_double>::cast(0.1e-6) / m_CAP();

			// FIXME: we need a possibility to remove entries from queue ...
			//        or an exact model ...
			m_clock.m_inc = netlist_time::from_double(0.5 / freq);
			//m_clock.update();

			//NL_VERBOSE_OUT(("{1} {2} {3} {4}\n", name(), v_freq, v_rng, freq));
		}

		if (!m_clock.m_enableq && m_ENQ())
		{
			m_clock.m_enableq = 1;
			m_clock.m_out = m_clock.m_out ^ 1;
			m_clock.m_Y.push(m_clock.m_out, netlist_time::from_nsec(1));
		}
		else if (m_clock.m_enableq && !m_ENQ())
		{
			m_clock.m_enableq = 0;
			m_clock.m_out = m_clock.m_out ^ 1;
			m_clock.m_Y.push(m_clock.m_out, netlist_time::from_nsec(1));
		}
	}

	NETLIB_DEVICE_IMPL(SN74LS629,     "SN74LS629",     "CAP")
	NETLIB_DEVICE_IMPL(SN74LS629_dip, "SN74LS629_DIP", "1.CAP1,2.CAP2")

	} //namespace devices
} // namespace netlist