Branch data Line data Source code
1 : : /*
2 : : * cpwline.cpp - coplanar waveguide line class implementation
3 : : *
4 : : * Copyright (C) 2004, 2005 Vincent Habchi, F5RCS <10.50@free.fr>
5 : : * Copyright (C) 2004, 2005, 2006, 2008 Stefan Jahn <stefan@lkcc.org>
6 : : *
7 : : * This is free software; you can redistribute it and/or modify
8 : : * it under the terms of the GNU General Public License as published by
9 : : * the Free Software Foundation; either version 2, or (at your option)
10 : : * any later version.
11 : : *
12 : : * This software is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : : * GNU General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU General Public License
18 : : * along with this package; see the file COPYING. If not, write to
19 : : * the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 : : * Boston, MA 02110-1301, USA.
21 : : *
22 : : * $Id$
23 : : *
24 : : */
25 : :
26 : : #if HAVE_CONFIG_H
27 : : # include <config.h>
28 : : #endif
29 : :
30 : : #include <limits>
31 : :
32 : : #include "component.h"
33 : : #include "substrate.h"
34 : : #include "cpwline.h"
35 : :
36 : : using namespace qucs;
37 : :
38 : 0 : cpwline::cpwline () : circuit (2) {
39 : 0 : Zl = Er = 0;
40 : 0 : type = CIR_CPWLINE;
41 : 0 : }
42 : :
43 : : /*! K(k)
44 : :
45 : : The function computes the complete elliptic integral of first kind
46 : : K() and the second kind E() using the arithmetic-geometric mean
47 : : algorithm (AGM) by Abramowitz and Stegun.
48 : : \todo move to common math
49 : : */
50 : 0 : void cpwline::ellipke (nr_double_t arg, nr_double_t &k, nr_double_t &e) {
51 : 0 : int iMax = 16;
52 [ # # ]: 0 : if (arg == 1.0) {
53 : 0 : k = std::numeric_limits<nr_double_t>::infinity();
54 : 0 : e = 0;
55 : : }
56 [ # # ][ # # ]: 0 : else if (std::isinf (arg) && arg < 0) {
[ # # ]
57 : 0 : k = 0;
58 : 0 : e = std::numeric_limits<nr_double_t>::infinity();
59 : : }
60 : : else {
61 : 0 : nr_double_t a, b, c, f, s, fk = 1, fe = 1, t, da = arg;
62 : : int i;
63 [ # # ]: 0 : if (arg < 0) {
64 : 0 : fk = 1 / qucs::sqrt (1 - arg);
65 : 0 : fe = qucs::sqrt (1 - arg);
66 : 0 : da = -arg / (1 - arg);
67 : : }
68 : 0 : a = 1;
69 : 0 : b = qucs::sqrt (1 - da);
70 : 0 : c = qucs::sqrt (da);
71 : 0 : f = 0.5;
72 : 0 : s = f * c * c;
73 [ # # ]: 0 : for (i = 0; i < iMax; i++) {
74 : 0 : t = (a + b) / 2;
75 : 0 : c = (a - b) / 2;
76 : 0 : b = qucs::sqrt (a * b);
77 : 0 : a = t;
78 : 0 : f *= 2;
79 : 0 : s += f * c * c;
80 [ # # ]: 0 : if (c / a < std::numeric_limits<nr_double_t>::epsilon()) break;
81 : : }
82 [ # # ]: 0 : if (i >= iMax) {
83 : 0 : k = 0; e = 0;
84 : : }
85 : : else {
86 : 0 : k = M_PI_2 / a;
87 : 0 : e = M_PI_2 * (1 - s) / a;
88 [ # # ]: 0 : if (arg < 0) {
89 : 0 : k *= fk;
90 : 0 : e *= fe;
91 : : }
92 : : }
93 : : }
94 : 0 : }
95 : :
96 : : /* We need to know only K(k), and if possible KISS. */
97 : 0 : nr_double_t cpwline::ellipk (nr_double_t k) {
98 : : nr_double_t r, lost;
99 [ # # ]: 0 : ellipke (k, r, lost);
100 : 0 : return r;
101 : : }
102 : :
103 : : /* More or less accurate approximation of K(k)/K'(k). Suggested by
104 : : publications dealing with coplanar components. */
105 : 0 : nr_double_t cpwline::ellipa (nr_double_t k) {
106 : : nr_double_t r, kp;
107 [ # # ]: 0 : if (k < M_SQRT1_2) {
108 : 0 : kp = qucs::sqrt (1 - k * k);
109 : 0 : r = M_PI / qucs::log (2 * (1 + qucs::sqrt (kp)) / (1 - qucs::sqrt (kp)));
110 : : }
111 : : else {
112 : 0 : r = qucs::log (2 * (1 + qucs::sqrt (k)) / (1 - qucs::sqrt (k))) / M_PI;
113 : : }
114 : 0 : return r;
115 : : }
116 : :
117 : 0 : void cpwline::initSP (void) {
118 : : // allocate S-parameter matrix
119 : 0 : allocMatrixS ();
120 : : // pre-compute propagation factors
121 : 0 : initPropagation ();
122 : 0 : }
123 : :
124 : 0 : void cpwline::initPropagation (void) {
125 : : // get properties of substrate and coplanar line
126 : 0 : nr_double_t W = getPropertyDouble ("W");
127 : 0 : nr_double_t s = getPropertyDouble ("S");
128 : 0 : substrate * subst = getSubstrate ();
129 : 0 : nr_double_t er = subst->getPropertyDouble ("er");
130 : 0 : nr_double_t h = subst->getPropertyDouble ("h");
131 : 0 : nr_double_t t = subst->getPropertyDouble ("t");
132 : 0 : int backMetal = !strcmp (getPropertyString ("Backside"), "Metal");
133 : 0 : int approx = !strcmp (getPropertyString ("Approx"), "yes");
134 : :
135 : 0 : tand = subst->getPropertyDouble ("tand");
136 : 0 : rho = subst->getPropertyDouble ("rho");
137 : 0 : len = getPropertyDouble ("L");
138 : :
139 : : // other local variables (quasi-static constants)
140 : 0 : nr_double_t k1, kk1, kpk1, k2, k3, q1, q2, q3 = 0, qz, er0 = 0;
141 : :
142 : : // compute the necessary quasi-static approx. (K1, K3, er(0) and Z(0))
143 : 0 : k1 = W / (W + s + s);
144 : 0 : kk1 = ellipk (k1);
145 : 0 : kpk1 = ellipk (qucs::sqrt (1 - k1 * k1));
146 [ # # ]: 0 : if (approx) {
147 : 0 : q1 = ellipa (k1);
148 : : } else {
149 : 0 : q1 = kk1 / kpk1;
150 : : }
151 : :
152 : : // backside is metal
153 [ # # ]: 0 : if (backMetal) {
154 : 0 : k3 = qucs::tanh ((M_PI / 4) * (W / h)) / qucs::tanh ((M_PI / 4) * (W + s + s) / h);
155 [ # # ]: 0 : if (approx) {
156 : 0 : q3 = ellipa (k3);
157 : : } else {
158 : 0 : q3 = ellipk (k3) / ellipk (qucs::sqrt (1 - k3 * k3));
159 : : }
160 : 0 : qz = 1 / (q1 + q3);
161 : 0 : er0 = 1 + q3 * qz * (er - 1);
162 : 0 : zl_factor = Z0 / 2 * qz;
163 : : }
164 : : // backside is air
165 : : else {
166 : 0 : k2 = qucs::sinh ((M_PI / 4) * (W / h)) / qucs::sinh ((M_PI / 4) * (W + s + s) / h);
167 [ # # ]: 0 : if (approx) {
168 : 0 : q2 = ellipa (k2);
169 : : } else {
170 : 0 : q2 = ellipk (k2) / ellipk (qucs::sqrt (1 - k2 * k2));
171 : : }
172 : 0 : er0 = 1 + (er - 1) / 2 * q2 / q1;
173 : 0 : zl_factor = Z0 / 4 / q1;
174 : : }
175 : :
176 : : // adds effect of strip thickness
177 [ # # ]: 0 : if (t > 0) {
178 : : nr_double_t d, se, We, ke, qe;
179 : 0 : d = (t * 1.25 / M_PI) * (1 + qucs::log (4 * M_PI * W / t));
180 : 0 : se = s - d;
181 : 0 : We = W + d;
182 : :
183 : : // modifies k1 accordingly (k1 = ke)
184 : 0 : ke = We / (We + se + se); // ke = k1 + (1 - k1 * k1) * d / 2 / s;
185 [ # # ]: 0 : if (approx) {
186 : 0 : qe = ellipa (ke);
187 : : } else {
188 : 0 : qe = ellipk (ke) / ellipk (qucs::sqrt (1 - ke * ke));
189 : : }
190 : : // backside is metal
191 [ # # ]: 0 : if (backMetal) {
192 : 0 : qz = 1 / (qe + q3);
193 : 0 : er0 = 1 + q3 * qz * (er - 1);
194 : 0 : zl_factor = Z0 / 2 * qz;
195 : : }
196 : : // backside is air
197 : : else {
198 : 0 : zl_factor = Z0 / 4 / qe;
199 : : }
200 : :
201 : : // modifies er0 as well
202 : 0 : er0 = er0 - (0.7 * (er0 - 1) * t / s) / (q1 + (0.7 * t / s));
203 : : }
204 : :
205 : : // pre-compute square roots
206 : 0 : sr_er = qucs::sqrt (er);
207 : 0 : sr_er0 = qucs::sqrt (er0);
208 : :
209 : : // cut-off frequency of the TE0 mode
210 : 0 : fte = (C0 / 4) / (h * qucs::sqrt (er - 1));
211 : :
212 : : // dispersion factor G
213 : 0 : nr_double_t p = qucs::log (W / h);
214 : 0 : nr_double_t u = 0.54 - (0.64 - 0.015 * p) * p;
215 : 0 : nr_double_t v = 0.43 - (0.86 - 0.54 * p) * p;
216 : 0 : G = qucs::exp (u * qucs::log (W / s) + v);
217 : :
218 : : // loss constant factors (computed only once for efficency sake)
219 : 0 : nr_double_t ac = 0;
220 [ # # ]: 0 : if (t > 0) {
221 : : // equations by GHIONE
222 : 0 : nr_double_t n = (1 - k1) * 8 * M_PI / (t * (1 + k1));
223 : 0 : nr_double_t a = W / 2;
224 : 0 : nr_double_t b = a + s;
225 : 0 : ac = (M_PI + qucs::log (n * a)) / a + (M_PI + qucs::log (n * b)) / b;
226 : : }
227 : 0 : ac_factor = ac / (4 * Z0 * kk1 * kpk1 * (1 - k1 * k1));
228 : 0 : ac_factor *= qucs::sqrt (M_PI * MU0 * rho); // Rs factor
229 : 0 : ad_factor = (er / (er - 1)) * tand * M_PI / C0;
230 : :
231 : 0 : bt_factor = 2 * M_PI / C0;
232 : 0 : }
233 : :
234 : 0 : void cpwline::calcAB (nr_double_t f, nr_double_t& zl, nr_double_t& al,
235 : : nr_double_t& bt) {
236 : 0 : nr_double_t sr_er_f = sr_er0;
237 : 0 : nr_double_t ac = ac_factor;
238 : 0 : nr_double_t ad = ad_factor;
239 : :
240 : : // by initializing as much as possible outside this function, the
241 : : // overhead is minimal
242 : :
243 : : // add the dispersive effects to er0
244 : 0 : sr_er_f += (sr_er - sr_er0) / (1 + G * qucs::pow (f / fte, -1.8));
245 : :
246 : : // computes impedance
247 : 0 : zl /= sr_er_f;
248 : :
249 : : // for now, the loss are limited to strip losses (no radiation
250 : : // losses yet) losses in neper/length
251 : 0 : ad *= f * (sr_er_f - 1 / sr_er_f);
252 : 0 : ac *= qucs::sqrt (f) * sr_er0;
253 : :
254 : 0 : al = ac + ad;
255 : 0 : bt *= sr_er_f * f;
256 : :
257 : 0 : Er = sqr (sr_er_f);
258 : 0 : Zl = zl;
259 : 0 : }
260 : :
261 : 0 : void cpwline::saveCharacteristics (nr_double_t) {
262 : 0 : setCharacteristic ("Zl", Zl);
263 : 0 : setCharacteristic ("Er", Er);
264 : 0 : }
265 : :
266 : 0 : void cpwline::calcSP (nr_double_t frequency) {
267 : :
268 : 0 : nr_double_t zl = zl_factor;
269 : 0 : nr_double_t beta = bt_factor;
270 : : nr_double_t alpha;
271 : :
272 [ # # ]: 0 : calcAB (frequency, zl, alpha, beta);
273 : :
274 : : // calculate and set S-parameters
275 : 0 : nr_double_t z = zl / z0;
276 : 0 : nr_double_t y = 1 / z;
277 : 0 : nr_complex_t g = nr_complex_t (alpha, beta);
278 [ # # ][ # # ]: 0 : nr_complex_t n = 2.0 * cosh (g * len) + (z + y) * sinh (g * len);
[ # # ]
279 [ # # ][ # # ]: 0 : nr_complex_t s11 = (z - y) * sinh (g * len) / n;
280 [ # # ]: 0 : nr_complex_t s21 = 2.0 / n;
281 : :
282 [ # # ][ # # ]: 0 : setS (NODE_1, NODE_1, s11); setS (NODE_2, NODE_2, s11);
283 [ # # ][ # # ]: 0 : setS (NODE_1, NODE_2, s21); setS (NODE_2, NODE_1, s21);
284 : 0 : }
285 : :
286 : : /* The function calculates the quasi-static impedance of a coplanar
287 : : waveguide line and the value of the effective dielectric constant
288 : : for the given coplanar line and substrate properties. */
289 : 0 : void cpwline::analyseQuasiStatic (nr_double_t W, nr_double_t s, nr_double_t h,
290 : : nr_double_t t, nr_double_t er, int backMetal,
291 : : nr_double_t& ZlEff, nr_double_t& ErEff) {
292 : :
293 : : // local variables (quasi-static constants)
294 : 0 : nr_double_t k1, k2, k3, q1, q2, q3 = 0, qz;
295 : :
296 : 0 : ErEff = er;
297 : 0 : ZlEff = 0;
298 : :
299 : : // compute the necessary quasi-static approx. (K1, K3, er(0) and Z(0))
300 : 0 : k1 = W / (W + s + s);
301 : 0 : q1 = ellipk (k1) / ellipk (qucs::sqrt (1 - k1 * k1));
302 : :
303 : : // backside is metal
304 [ # # ]: 0 : if (backMetal) {
305 : 0 : k3 = qucs::tanh ((M_PI / 4) * (W / h)) / qucs::tanh ((M_PI / 4) * (W + s + s) / h);
306 : 0 : q3 = ellipk (k3) / ellipk (qucs::sqrt (1 - k3 * k3));
307 : 0 : qz = 1 / (q1 + q3);
308 : 0 : ErEff = 1 + q3 * qz * (er - 1);
309 : 0 : ZlEff = Z0 / 2 * qz;
310 : : }
311 : : // backside is air
312 : : else {
313 : 0 : k2 = qucs::sinh ((M_PI / 4) * (W / h)) / qucs::sinh ((M_PI / 4) * (W + s + s) / h);
314 : 0 : q2 = ellipk (k2) / ellipk (qucs::sqrt (1 - k2 * k2));
315 : 0 : ErEff = 1 + (er - 1) / 2 * q2 / q1;
316 : 0 : ZlEff = Z0 / 4 / q1;
317 : : }
318 : :
319 : : // adds effect of strip thickness
320 [ # # ]: 0 : if (t > 0) {
321 : : nr_double_t d, se, We, ke, qe;
322 : 0 : d = (t * 1.25 / M_PI) * (1 + qucs::log (4 * M_PI * W / t));
323 : 0 : se = s - d;
324 : 0 : We = W + d;
325 : :
326 : : // modifies k1 accordingly (k1 = ke)
327 : 0 : ke = We / (We + se + se); // ke = k1 + (1 - k1 * k1) * d / 2 / s;
328 : 0 : qe = ellipk (ke) / ellipk (qucs::sqrt (1 - ke * ke));
329 : :
330 : : // backside is metal
331 [ # # ]: 0 : if (backMetal) {
332 : 0 : qz = 1 / (qe + q3);
333 : 0 : ErEff = 1 + q3 * qz * (er - 1);
334 : 0 : ZlEff = Z0 / 2 * qz;
335 : : }
336 : : // backside is air
337 : : else {
338 : 0 : ZlEff = Z0 / 4 / qe;
339 : : }
340 : :
341 : : // modifies ErEff as well
342 : 0 : ErEff = ErEff - (0.7 * (ErEff - 1) * t / s) / (q1 + (0.7 * t / s));
343 : : }
344 : 0 : ErEff = qucs::sqrt (ErEff);
345 : 0 : ZlEff /= ErEff;
346 : 0 : }
347 : :
348 : : /* This function calculates the frequency dependent value of the
349 : : effective dielectric constant and the coplanar line impedance for
350 : : the given frequency. */
351 : 0 : void cpwline::analyseDispersion (nr_double_t W, nr_double_t s, nr_double_t h,
352 : : nr_double_t er, nr_double_t ZlEff,
353 : : nr_double_t ErEff, nr_double_t frequency,
354 : : nr_double_t& ZlEffFreq,
355 : : nr_double_t& ErEffFreq) {
356 : : // local variables
357 : : nr_double_t fte, G;
358 : :
359 : 0 : ErEffFreq = ErEff;
360 : 0 : ZlEffFreq = ZlEff * ErEff;
361 : :
362 : : // cut-off frequency of the TE0 mode
363 : 0 : fte = (C0 / 4) / (h * qucs::sqrt (er - 1));
364 : :
365 : : // dispersion factor G
366 : 0 : nr_double_t p = qucs::log (W / h);
367 : 0 : nr_double_t u = 0.54 - (0.64 - 0.015 * p) * p;
368 : 0 : nr_double_t v = 0.43 - (0.86 - 0.54 * p) * p;
369 : 0 : G = qucs::exp (u * qucs::log (W / s) + v);
370 : :
371 : : // add the dispersive effects to er0
372 : 0 : ErEffFreq += (qucs::sqrt (er) - ErEff) / (1 + G * qucs::pow (frequency / fte, -1.8));
373 : :
374 : : // computes impedance
375 : 0 : ZlEffFreq /= ErEffFreq;
376 : 0 : }
377 : :
378 : 0 : void cpwline::calcNoiseSP (nr_double_t) {
379 : : // calculate noise using Bosma's theorem
380 [ # # ]: 0 : nr_double_t T = getPropertyDouble ("Temp");
381 [ # # ]: 0 : matrix s = getMatrixS ();
382 [ # # ]: 0 : matrix e = eye (getSize ());
383 [ # # ][ # # ]: 0 : setMatrixN (kelvin (T) / T0 * (e - s * transpose (conj (s))));
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
384 : 0 : }
385 : :
386 : 0 : void cpwline::initDC (void) {
387 : : // a DC short (voltage source V = 0 volts)
388 : 0 : setVoltageSources (1);
389 : 0 : setInternalVoltageSource (1);
390 : 0 : allocMatrixMNA ();
391 : 0 : clearY ();
392 : 0 : voltageSource (VSRC_1, NODE_1, NODE_2);
393 : 0 : }
394 : :
395 : 0 : void cpwline::initTR (void) {
396 : 0 : initDC ();
397 : 0 : }
398 : :
399 : 0 : void cpwline::initAC (void) {
400 : 0 : setVoltageSources (0);
401 : 0 : allocMatrixMNA ();
402 : 0 : initPropagation ();
403 : 0 : }
404 : :
405 : 0 : void cpwline::calcAC (nr_double_t frequency) {
406 : :
407 : 0 : nr_double_t zl = zl_factor;
408 : 0 : nr_double_t beta = bt_factor;
409 : : nr_double_t alpha;
410 : :
411 [ # # ]: 0 : calcAB (frequency, zl, alpha, beta);
412 : :
413 : : // calculate and set Y-parameters
414 : 0 : nr_complex_t g = nr_complex_t (alpha, beta);
415 [ # # ]: 0 : nr_complex_t y11 = coth (g * len) / zl;
416 [ # # ]: 0 : nr_complex_t y21 = -cosech (g * len) / zl;
417 : :
418 [ # # ][ # # ]: 0 : setY (NODE_1, NODE_1, y11); setY (NODE_2, NODE_2, y11);
419 [ # # ][ # # ]: 0 : setY (NODE_1, NODE_2, y21); setY (NODE_2, NODE_1, y21);
420 : 0 : }
421 : :
422 : 0 : void cpwline::calcNoiseAC (nr_double_t) {
423 : : // calculate noise using Bosma's theorem
424 : 0 : nr_double_t T = getPropertyDouble ("Temp");
425 [ # # ][ # # ]: 0 : setMatrixN (4 * kelvin (T) / T0 * real (getMatrixY ()));
[ # # ][ # # ]
[ # # ]
426 : 0 : }
427 : :
428 : : // properties
429 : : PROP_REQ [] = {
430 : : { "W", PROP_REAL, { 1e-3, PROP_NO_STR }, PROP_POS_RANGE },
431 : : { "S", PROP_REAL, { 1e-3, PROP_NO_STR }, PROP_POS_RANGE },
432 : : { "L", PROP_REAL, { 10e-3, PROP_NO_STR }, PROP_POS_RANGE },
433 : : { "Subst", PROP_STR, { PROP_NO_VAL, "Subst1" }, PROP_NO_RANGE },
434 : : PROP_NO_PROP };
435 : : PROP_OPT [] = {
436 : : { "Temp", PROP_REAL, { 26.85, PROP_NO_STR }, PROP_MIN_VAL (K) },
437 : : { "Backside", PROP_STR, { PROP_NO_VAL, "Metal" },
438 : : PROP_RNG_STR2 ("Metal", "Air") },
439 : : { "Approx", PROP_STR, { PROP_NO_VAL, "no" }, PROP_RNG_YESNO },
440 : : PROP_NO_PROP };
441 : : struct define_t cpwline::cirdef =
442 : : { "CLIN", 2, PROP_COMPONENT, PROP_NO_SUBSTRATE, PROP_LINEAR, PROP_DEF };
|